Is there a way to set up a global error handler for a MAUI app? My app is currently running fine when I launch it from Visual Studio. However on all simulators if I try and start the app up from the shell's app icon, it starts up then quits (and on iOS I get a crash dump). I have no idea where this is happening at, so wanted to try and catch in a global handler.
I looked at builder.ConfigureMauiHandlers, but it appears to only work with controls. You can't use a type of UnhandledExceptionEventHandler, which is what I was thinking would be appropriate.
I also tried an AppDomain handler, but it doesn't not appear to work either:
AppDomain ad = AppDomain.CurrentDomain;
ad.UnhandledException += Ad_UnhandledException;
Is this possible? I've only found samples for WCF and they don't work.
There is a pretty lengthy GitHub discussion here: Global exception handling #653
The most popular response is from one of the Sentry developers, who offered a gist solution using first chance exception.
The windows-specific implementation includes the following handlers, where _lastFirstChanceException
is a private static variable.
AppDomain.CurrentDomain.UnhandledException += (sender, args) =>
{
UnhandledException?.Invoke(sender, args);
};
...
AppDomain.CurrentDomain.FirstChanceException += (_, args) =>
{
_lastFirstChanceException = args.Exception;
};
Microsoft.UI.Xaml.Application.Current.UnhandledException += (sender, args) =>
{
var exception = args.Exception;
if (exception.StackTrace is null)
{
exception = _lastFirstChanceException;
}
UnhandledException?.Invoke(sender, new UnhandledExceptionEventArgs(exception, true));
};
While this worked for my use case, SonarLint did indicate that there were some issues with the static constructor approach. One solution to that would be to use a sealed MauiException
class with a Lazy<MauiException>
to access it as a threadsafe singleton, as Jon Skeet describes.
For an app targeting iOS and Android this should be more than enough:
public static class GlobalExceptionHandler
{
// We'll route all unhandled exceptions through this one event.
public static event UnhandledExceptionEventHandler UnhandledException;
static GlobalExceptionHandler()
{
AppDomain.CurrentDomain.UnhandledException += (sender, args) =>
{
UnhandledException?.Invoke(sender, args);
};
// Events fired by the TaskScheduler. That is calls like Task.Run(...)
TaskScheduler.UnobservedTaskException += (sender, args) =>
{
UnhandledException?.Invoke(sender, new UnhandledExceptionEventArgs(args.Exception, false));
};
#if IOS
// For iOS and Mac Catalyst
// Exceptions will flow through AppDomain.CurrentDomain.UnhandledException,
// but we need to set UnwindNativeCode to get it to work correctly.
//
// See: https://github.com/xamarin/xamarin-macios/issues/15252
ObjCRuntime.Runtime.MarshalManagedException += (_, args) =>
{
args.ExceptionMode = ObjCRuntime.MarshalManagedExceptionMode.UnwindNativeCode;
};
#elif ANDROID
// For Android:
// All exceptions will flow through Android.Runtime.AndroidEnvironment.UnhandledExceptionRaiser,
// and NOT through AppDomain.CurrentDomain.UnhandledException
Android.Runtime.AndroidEnvironment.UnhandledExceptionRaiser += (sender, args) =>
{
args.Handled = true;
UnhandledException?.Invoke(sender, new UnhandledExceptionEventArgs(args.Exception, true));
};
Java.Lang.Thread.DefaultUncaughtExceptionHandler = new CustomUncaughtExceptionHandler(e =>
UnhandledException?.Invoke(null, new UnhandledExceptionEventArgs(e, true)));
#endif
}
}
#if ANDROID
public class CustomUncaughtExceptionHandler(Action<Java.Lang.Throwable> callback)
: Java.Lang.Object, Java.Lang.Thread.IUncaughtExceptionHandler
{
public void UncaughtException(Java.Lang.Thread t, Java.Lang.Throwable e)
{
callback(e);
}
}
#endif
And a simple way to use this would be:
GlobalExceptionHandler.UnhandledException += GlobalExceptionHandler_UnhandledException;
private void GlobalExceptionHandler_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
var exception = e.ExceptionObject as System.Exception;
// Log this exception or whatever
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With