Handling and intercepting Back button Navigation in Xamarin Forms Shell

I’ve recently ended up needing to ask if the user really wants to navigate away from a page in my Xamarin app, Net Writer. Essentially whilst a post is being edited I don’t want the user to accidentally lose their progress, necessitating the need to inject a “Are you sure?” or “Confirm exit” prompt when the user presses either the Android hardware or OS level back button or the back button on the navigation bar provided by the Xamarin Forms Shell.

I found a blog post by Bohdan Benetskyi from March 2020, but it was geared for MvvmCross and not Xamarin Forms Shell. Here is how to adapt it.

Introducing IBackButtonHandler to your Page

If you have existing pages you can add an interface to them and implement it, for example here is a page from my app with IBackButtonHandler added to the class:

  [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial clas PostListSummaryPage : ReactiveContentPage<PostSummaryListPageViewModel>, IBackButtonHandler
    {

This can then be implemented however you like, returning true if the back navigation should be aborted, for example:

public async Task<bool> HandleBackButton()
{
    if (await ShouldNotGoBack())
    {
        return true;
    }

    return false;
}

public async Task<bool> ShouldNotGoBack()
{
    var response = await this.DisplayActionSheet("Would you like to discard any unsaved changes to this post?", "No", "Yes, go back");

    return response == "No";
}

Once that is done next up is wiring up the back buttons.

Handling the Android OS back button

To handle the OS level back button you’ll need to add the following to MainActivity.cs:

public async override void OnBackPressed()
{
    var backButtonHandler = Shell.Current.CurrentPage as IBackButtonHandler;

    if (backButtonHandler == null)
    {
        base.OnBackPressed();
        return;
    }

    var backButtonHandled = await backButtonHandler.HandleBackButton();
    if (!backButtonHandled)
    {
        base.OnBackPressed();
    }
}

Lets step through this. First up, it attempts to cast the current Shell page as IBackButtonHandler. This means that only Pages that implement this interface will have any change of behaviour here. If the interface is not implemented, the base implementation of OnBackPressed() is called. If it is implemented, the HandleBackButton method is evaluated. If it returns true, the back navigation is cancelled. If false, the base implementation of OnBackPressed() is called, continuing the back navigation.

Handling the Shell back button

This is the button on the top left that shows when you are more than one level deep in the navigation stack. Handling this is less clean as it requires adding a call to Shell.SetBackButtonBehaviour in the constructor of each page that requires this. For example, add the following to the constructor of your page, after InitializeComponent():

Shell.SetBackButtonBehavior(this, new BackButtonBehavior()
{
    Command = new Command(async () => {

        var backButtonHandled = await this.HandleBackButton();
        if (!backButtonHandled)
        {
            await Navigation.PopAsync();
        }

    })
});

After that, you’l get a nice prompt pop up whenever you hit the OS back button or the shell navigation back button, but only on pages that implement IBackButtonHandler.

Walkthough video

If you would like a video walkthough of the above, check out this video: