Global errors handling in Xamarin Forms

Contenido

Many times we may encounter unhandled exceptions that are very difficult to detect and log, and you must do so in order to handle errors in your application. One approach is to use App Center, but it is always ideal to be able to register what happened and then handle the information or send it to web service.

💡 TIP: Exceptions are best handled near the point at which they are thrown.

There are global handlers on each platform to allow you to receive notifications of exceptions that you haven’t handled elsewhere. Note that these do NOT exist to allow you to catch an exception and continue execution as if nothing had happened.

If you don’t handle exceptions near where they were thrown and one of the global handlers is activated, don’t expect to be able to recover. Your application is likely to be in an unknown state at that time, so it is likely not safe to continue running.

⚠ Caution: Exception handling is best added at the beginning of development. Trying to adapt to it at the end of development will be painful.

Global errors in Xamarin Forms

The following code is the one we use right now and it has helped us a lot, especially for unmanaged exceptions tasks that are simply silent, that is, they do not block the application, but you still want to handle them.

🚀 Note: The code is shown below also includes the ability to display the log when you are debugging the application. Of course, you can implement your own logging or handling methods.

Android

...
protected override void OnCreate(Bundle bundle)
{
    base.OnCreate(bundle);  
     
    AppDomain.CurrentDomain.UnhandledException += CurrentDomainOnUnhandledException;
    TaskScheduler.UnobservedTaskException += TaskSchedulerOnUnobservedTaskException;  
     
    Xamarin.Forms.Forms.Init(this, bundle);  
    DisplayCrashReport();  
     
    var app = new App();  
    LoadApplication(app);
}  
 
‪#‎region‬ Error handling
private static void TaskSchedulerOnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs unobservedTaskExceptionEventArgs)
{
    var newExc = new Exception("TaskSchedulerOnUnobservedTaskException", unobservedTaskExceptionEventArgs.Exception);
    LogUnhandledException(newExc);
}  
 
private static void CurrentDomainOnUnhandledException(object sender, UnhandledExceptionEventArgs unhandledExceptionEventArgs)
{
    var newExc = new Exception("CurrentDomainOnUnhandledException", unhandledExceptionEventArgs.ExceptionObject as Exception);
    LogUnhandledException(newExc);
}  
 
internal static void LogUnhandledException(Exception exception)
{
    try
    {
        const string errorFileName = "Fatal.log";
        var libraryPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal); // iOS: Environment.SpecialFolder.Resources
        var errorFilePath = Path.Combine(libraryPath, errorFileName);  
        var errorMessage = String.Format("Time: {0}\r\nError: Unhandled Exception\r\n{1}",
        DateTime.Now, exception.ToString());
        File.WriteAllText(errorFilePath, errorMessage);  
         
        // Log to Android Device Logging.
        Android.Util.Log.Error("Crash Report", errorMessage);
    }
    catch
    {
        // just suppress any error logging exceptions
    }
}  
 
/// <summary>
// If there is an unhandled exception, the exception information is diplayed 
// on screen the next time the app is started (only in debug configuration)
/// </summary>
[Conditional("DEBUG")]
private void DisplayCrashReport()
{
    const string errorFilename = "Fatal.log";
    var libraryPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
    var errorFilePath = Path.Combine(libraryPath, errorFilename);
 
    if (!File.Exists(errorFilePath))
    {
        return; 
    }
     
    var errorText = File.ReadAllText(errorFilePath);
    new AlertDialog.Builder(this)
        .SetPositiveButton("Clear", (sender, args) =>
        {
            File.Delete(errorFilePath);
        })
        .SetNegativeButton("Close", (sender, args) =>
        {
            // User pressed Close.
        })
        .SetMessage(errorText)
        .SetTitle("Crash Report")
        .Show();
} 
 
‪#‎endregion‬  
...

⚠ Note: TaskScheduler.UnobservedTaskException does not get fire immediately. You may have to wait a while before it does.

iOS

Unlike Android, you should use the FinishedLaunching method, not the Main method in the AppDelegate.cs.

...
public override bool FinishedLaunching(UIApplication uiApplication, NSDictionary options)
{
    AppDomain.CurrentDomain.UnhandledException += CurrentDomainOnUnhandledException;
    TaskScheduler.UnobservedTaskException += TaskSchedulerOnUnobservedTaskException;  
    // Rest of your code...
}  

/// <summary>
// If there is an unhandled exception, the exception information is diplayed 
// on screen the next time the app is started (only in debug configuration)
/// </summary>
[Conditional("DEBUG")]
private static void DisplayCrashReport()
{
    const string errorFilename = "Fatal.log";
    var libraryPath = Environment.GetFolderPath(Environment.SpecialFolder.Resources);
    var errorFilePath = Path.Combine(libraryPath, errorFilename);

    if (!File.Exists(errorFilePath))
    {
        return;
    }

    var errorText = File.ReadAllText(errorFilePath);
    var alertView = new UIAlertView("Crash Report", errorText, null, "Close", "Clear") { UserInteractionEnabled = true };
    alertView.Clicked += (sender, args) =>
    {
        if (args.ButtonIndex != 0)
        {
            File.Delete(errorFilePath);
        }
    };
    alertView.Show();
}
...

UWP

In UWP we can use UnhandledException; However, it only catches exceptions that arise through the XAML framework (UI) and you don’t always get much information about what the root cause is, even in InnerException.

...
public App()
{
    // Global handler for uncaught exceptions.
    UnhandledException += Application_UnhandledException;

    InitializeComponent();
}

private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
{
    if (Debugger.IsAttached)
    {
        // An unhandled exception has occurred; break into the debugger
        Debugger.Break();
    }
}
...

What you should keep in mind

Managed exceptions

For managed exceptions, you must deal with that yourself (at the top level of the UI) and let the lower layers throw exceptions back through the stack.

Similarly, you can have a generic asynchronous execution function in your BaseViewModel that handles exceptions in a standardized manner. In other exceptions where you need to change the UX behavior based on an exception, implement try-catch for that area.

If you want to know more about this topic, just let me know on my Social Media.

App center

Note that in most cases AppCenter will automatically catch unmanaged exceptions, and also detects failures when restarting.

On the other hand, trying to catch exceptions at a specific platform level can take a while and the application may be in too unstable a state to continue in many cases.

🚨 Note: Note that in some cases AppCenter will not be able to catch all exceptions, even if this can be detected globally. These are very specific cases, where anything can affect but it’s good to mention it.

Conclusion

I hope this article will be very useful to you, and for any comments leave it in the box below. If you want a second part about how to handle your issues in your business logic globally let me know on my social media and find out all the content I’m sharing.

With nothing else to add, see you next time!

What do you think of this content?
 
Luis Matos

Luis Matos

I help professionals and companies to create value solutions. I am a Systems Engineer, blockchain executive, and international mobile application speaker. Founder of the Malla Consulting Agency and several international technology communities.
Subscribe
Notify of
2 Comments
Oldest
Newest
Inline Feedbacks
View all comments
2
0
Would love your thoughts, please comment.x
()
x

Search in the site