Modifying UWP navigation backstack with MvvmCross

WinUI

7 years ago

While developing mobile apps, you may encounter the need to clear or pop the navigation stack to remove specific pages from appearing when the user navigates back. Because MvvmCross framework has a lot of abstractions above the target operating systems, it does not contain a built-in mechanism to manipulate the back stack. How can we use the framework capabilities to implement this requirement in a clean fashion?

Motivation

There are two very common scenarios where you may need to manipulate the back stack of your application. If your app has a user area and login, you probably want your user to enter login view, enter his credentials and enter the user dashboard. After that navigating back to the login screen would make little sense as the user is already logged in. The solution is to pop the last entry in the navigation stack after navigating to the user area.

Login flow

Login flow

Second common scenario involving back stack is jumping to home view after the user navigates deep into your app. In this case we want to navigate home and clear the whole back stack afterwards.

Jumping home

Jumping home

UWP back stack 101

Universal Windows Platform uses the Frame control to manage page navigation. The frame acts as a container which displays pages and maintains the navigation state. The most important property for us is the BackStack property, which is an IList of PageStackEntry objects. Because the BackStack is an IList , we can perform all the classic list operations on it. Hence, we can create two simple methods - one that pops the stack (removes the last entry) and one that clears it completely.

private void HandlePopBackstackHint()
{
    if (_frame.CanGoBack)
    {
        _frame.BackStack.RemoveAt(_frame.BackStackDepth - 1);
    }
}

private void HandleClearBackstackHint()
{
    _frame.BackStack.Clear();
}

As you can see, we can use BackStackDepth property to query the current depth of the back stack. Equivalently you can use BackStack.Count . For those who are curious - in addition to the back stack, the Frame also maintains a forward stack, which means you could set up a navigation flow very similar to a classic internet browser with back and forward arrows just using the built-in frame control.

Integrating into MvvmCross

The logic for back stack manipulation is required in the ViewModel to be able to control when the specific steps of the stack should be removed. Unfortunately, because the specifics of the navigation are tied to the operating system, we cannot directly call our methods defined above from a portable class library. Although we could build a service that does this for us, MvvmCross provides a cleaner solution in the form of presentation hints. Presentation hints are classes that derive from MvxPresentationHint and represent an intention to change the user interface presentation. There is one built-in presentation hint in MvvmCross you have probably already met - the MvxClosePresentationHint . This hint is being created when you call the Close method in your ViewModel and is handled by default by navigating back.

Creating Presentation Hints

The presentation hints should be defined in the Core PCL project and by convention stored in a PresentationHints folder/namespace.

namespace MvvmCrossBackstack.Core.PresentationHints
{
    public class ClearBackstackHint : MvxPresentationHint
    {
    }

    public class PopBackstackHint : MvxPresentationHint
    {
    }
}

Both our hints are just empty classes, because apart from the type of the hint we don't need any additional info. Nothing stops you from building more complex hints with custom properties, however.

Changing presentation from ViewModel

MvxNavigatingObject class (which MvxViewModel derives from) has a ChangePresentation method which accepts one parameter of type MvxPresentationHint . The usage is very simple:

private void GoToUserArea()
{
    //navigate to user area
    ShowViewModel<UserDashboardViewModel>();
    //pop the back stack to remove the login page from back stack
    ChangePresentation(new PopBackstackHint());
}

private void GoHome()
{
    //navigate to main page
    ShowViewModel<MainViewModel>();
    //clear the whole back stack            
    ChangePresentation(new ClearBackstackHint());
}

Handling the presentation change request

The presentation hint is raised in the PCL but has to be handled in a platform specific manner inside the app project itself. MvvmCross implements a concept of view presenters. These implement the IMvxViewPresenter interface which has the following signature:

public interface IMvxViewPresenter
{
    void AddPresentationHintHandler<THint>(Func<THint, bool> action)
        where THint : MvxPresentationHint;
    void ChangePresentation(MvxPresentationHint hint);
    void Show(MvxViewModelRequest request);
}

As you can see, the view presenters already have a built-in support for presentation hints. Each platform implements a default view presenter, for Windows we have the MvxWindowsViewPresenter , which has the default behavior of Frame navigation implemented. You may implement more complex view presenters if your app requires such. The default is sufficient for us, because we will just use the AddPresentationHintHandler method to register handlers for our presentation hints. First we create a class that takes a IMvxWindowsFrame (which is a wrapper around the Frame control) and contains handlers for our hints:

/// <summary>
/// Handles back stack presentation hints
/// </summary>
public class BackStackHintHandler
{
    private readonly Frame _frame;

    public BackStackHintHandler(IMvxWindowsFrame rootFrame)
    {
        _frame = (Frame)rootFrame.UnderlyingControl;
    }

    public bool HandleClearBackstackHint(
        ClearBackstackHint clearBackstackHint)
    {
        _frame.BackStack.Clear();
        UpdateBackButtonVisibility();
        return true;
    }

    public bool HandlePopBackstackHint(
        PopBackstackHint clearBackstackHint)
    {
        if (_frame.CanGoBack)
        {
            _frame.BackStack.RemoveAt(_frame.BackStackDepth - 1);
            UpdateBackButtonVisibility();
            return true;
        }
        return false;
    }

    private void UpdateBackButtonVisibility()
    {
        SystemNavigationManager.
            GetForCurrentView().AppViewBackButtonVisibility =
                _frame.CanGoBack ? 
                    AppViewBackButtonVisibility.Visible :
                    AppViewBackButtonVisibility.Collapsed;
    }
}

In addition to manipulating the back stack we are returning true or false if the hint was handled or was not, respectively. This is useful if we want to have multiple different handers for a single presentation hint. Now the last thing left to do is to register our handlers. We can do this by overriding the CreateViewPresenter method in Setup class of the UWP project.

protected override IMvxWindowsViewPresenter CreateViewPresenter(
    IMvxWindowsFrame rootFrame )
{
    var viewPresenter = base.CreateViewPresenter(rootFrame);
    var backStackHandler = new BackStackHintHandler(rootFrame);
    viewPresenter.AddPresentationHintHandler<ClearBackstackHint>
       (backStackHandler.HandleClearBackstackHint);
    viewPresenter.AddPresentationHintHandler<PopBackstackHint>
       (backStackHandler.HandlePopBackstackHint);
    return viewPresenter;
}

We let the framework create the default view presenter and then add our two presentation hint handlers. This way they are registered and will be invoked when presentation change is hinted.

Source code

Sample source code for this post is available on my GitHub. This includes a working example app.

Summary

We have shown how we can manipulate the back stack in MvvmCross-based app in a clean manner. To accomplish this we took advantage of framework's presentation hints which are then handled in the app's view presenter.