Handle navigation exceptions in Prism for Xamarin

Ship’s Wheel Faaborg Sunset
Photo by Maximilian Weisbecker / Unsplash

If you're using the NavigationService in Prism, for Xamarin.Forms or simply Xamarin, like this :

navigationService.NavigateAsync(uri, parameters);

you might notice native crashes with no information or whatsoever.

This happens when during the loading of the page and/or the navigation to it an exception is thrown. In that case you actually won't see any exception, the app will just crash and you will spend hours looking for an answer.

The reason for that is that the NavigateAsync function is "awaitable" and returns a INavigationResult :

namespace Prism.Navigation
{
    public interface INavigationResult
    {
        bool Success { get; }

        Exception Exception { get; }
    }

    public class NavigationResult : INavigationResult
    {
        public bool Success { get; set; }

        public Exception Exception { get; set; }
    }
}

In order to handle this exception here is what I do :

Method extensions

I extend NavigateAsync functions with a TryNavigateAsync equivalent. The role of it is to handle the result of the navigation once it's done.

public static async Task TryNavigateAsync(this INavigationService navigationService, Uri uri, INavigationParameters parameters = null)
{
    var result = await navigationService.NavigateAsync(uri, parameters);
    HandleNavigationResult(result);
}

public static async Task TryNavigateModallyAsync(this INavigationService navigationService, Uri uri, INavigationParameters parameters = null)
{
    var result = await navigationService.NavigateAsync(uri, parameters, true);
    HandleNavigationResult(result);
}

public static async Task TryNavigateAsync(this INavigationService navigationService, string path, INavigationParameters parameters = null)
{
    var result = await navigationService.NavigateAsync(path, parameters);
    HandleNavigationResult(result);
}

public static async Task TryNavigateModallyAsync(this INavigationService navigationService, string path, INavigationParameters parameters = null)
{
    var result = await navigationService.NavigateAsync(path, parameters, true);
    HandleNavigationResult(result);
}

public static async Task TryNavigateBackAsync(this INavigationService navigationService, INavigationParameters parameters = null)
{
    var result = await navigationService.GoBackAsync(parameters);
    HandleNavigationResult(result);
}

That enables you to use this line instead :

// navigationService.NavigateAsync(uri, parameters);
navigationService.TryNavigateAsync(uri, parameters);

Handle INavigationResult

You want to test if your navigation was a success and, if not, do something with that exception :

private static void HandleNavigationResult(INavigationResult navigationResult)
{
    if (!navigationResult.Success)
    {
        Exception ex = new InvalidNavigationException();
        if (navigationResult.Exception != null)
            ex = navigationResult.Exception;
        SetMainPageFromException(ex);
    }
}

I created a new Exception for better logging purposes : InvalidNavigationException(), but if the inner navigation is given by Prism I will used the Prism one.

Display the exception or throw it

You can simply throw that exception if you want to. You might also just display it to yourself or in debug mode. Here is a little snippet to display that exception :

private static void SetMainPageFromException(Exception ex)
{
    var layout = new StackLayout
    {
        Padding = new Thickness(40)
    };
    layout.Children.Add(new Label
    {
        Text = ex?.GetType()?.Name ?? "Unknown Error encountered",
        FontAttributes = FontAttributes.Bold,
        HorizontalOptions = LayoutOptions.Center
    });

    layout.Children.Add(new ScrollView
    {
        Content = new Label
        {
            Text = $"{ex}",
            LineBreakMode = LineBreakMode.WordWrap
        }
    });

    PrismApplicationBase.Current.MainPage = new ContentPage
    {
        Content = layout
    };
}

See the sample of all the code above being used in my Xamarin Developer Sample :

Hope it helped 😉