Home > C#, Xamarin > A simple NavigationService for Xamarin.Forms

A simple NavigationService for Xamarin.Forms

11/07/2016

If we do a search on the Internet, we’ll find a lot of implementations of a NavigationService for Xamarin.Forms. While all of them are surely valid, there are two things that I don’t like much:

  • if we want to pass arguments to the new page, we need to use the constructor of the page itself (i.e., adding an object parameter to it);
  • they don’t provide a convenient way to clear navigation history, that is often a requirement (i.e., after login/registration).

So, I ended up writing my own implementation. It is very simple and doesn’t cover all the scenarios, but at this moment it completely satifies my need:

public class NavigationService
{
    private Dictionary<string, Type> pages { get; }
        = new Dictionary<string, Type>();

    public Page MainPage => Application.Current.MainPage;

    public void Configure(string key, Type pageType) => pages[key] = pageType;

    public void GoBack() => MainPage.Navigation.PopAsync();

    public void NavigateTo(string pageKey, object parameter = null,
        HistoryBehavior historyBehavior = HistoryBehavior.Default)
    {
        Type pageType;
        if (pages.TryGetValue(pageKey, out pageType))
        {
            var displayPage = (Page)Activator.CreateInstance(pageType);
            displayPage.SetNavigationArgs(parameter);

            if (historyBehavior == HistoryBehavior.ClearHistory)
            {
                MainPage.Navigation.InsertPageBefore(displayPage,
                    MainPage.Navigation.NavigationStack[0]);

                var existingPages = MainPage.Navigation.NavigationStack.ToList();
                for (int i = 1; i < existingPages.Count; i++)
                    MainPage.Navigation.RemovePage(existingPages[i]);
            }
            else
            {
                MainPage.Navigation.PushAsync(displayPage);
            }
        }
        else
        {
            throw new ArgumentException($"No such page: {pageKey}.",
                nameof(pageKey));
        }
    }
}

public enum HistoryBehavior
{
    Default,
    ClearHistory
}

public static class NavigationExtensions
{
    private static ConditionalWeakTable<Page, object> arguments
        = new ConditionalWeakTable<Page, object>();

    public static object GetNavigationArgs(this Page page)
    {
        object argument = null;
        arguments.TryGetValue(page, out argument);

        return argument;
    }

    public static void SetNavigationArgs(this Page page, object args)
        => arguments.Add(page, args);
}

The idea of this NavigationService is to first register all the pages using the Configure method (line 8), just like the MVVM Light approach. The core of the implementation is the NavigateTo method (lines 12-41). It tries to get the page type corresponding the passed key (line 16) and then instantiate it using Activator.CreateInstance.

This is the first difference with other solutions: we create the page using its default parameterless constructor. The actual parameter, if any, is stored in a kind of Dictionary using the SetNavigationArgs extension method at line 19 (its implementation is at lines 62-63).

Then, we check whether we want to clear history after navigation: if this case, we insert the new page before the current one (lines 23-24) and then, at lines 26-28, we remove all the other pages from the stack. Otherwise, at line 32 we just perform a normal PushAsync of the page.

Let’s see how to use it. First of all, we configure the NavigationService in the App class:

public partial class App : Application
{
    public static NavigationService NavigationService { get; }
        = new NavigationService();

    public App()
    {
        InitializeComponent();

        NavigationService.Configure("MainPage", typeof(MainPage));
        NavigationService.Configure("SecondPage", typeof(SecondPage));
        NavigationService.Configure("ThirdPage", typeof(ThirdPage));

        MainPage = new NavigationPage(new MainPage());
    }
}

Then, in MainPage.xaml we write something like this:

<StackLayout Margin="12" HorizontalOptions="Center">
  <Entry x:Name="arguments" Placeholder="Arguments..." />
  <StackLayout Orientation="Horizontal" Spacing="0">
    <Label Text="Clear History" VerticalOptions="Center" Margin="0,0,10,0" />
    <Switch x:Name="clearHistory" VerticalOptions="Center" />
  </StackLayout>
  <Button x:Name="navigate" Text="Go to second page" Clicked="navigate_Clicked" />
</StackLayout>

So we have an Entry that allows to specify arguments to be passed to the target (line 2), a Switch to clear history after navigation (line 5) and a Button to actually go to the second page (line 7). The corresponding code-behind follows:

private void navigate_Clicked(object sender, EventArgs e)
{
    var historyBehavior = clearHistory.IsToggled
        ? HistoryBehavior.ClearHistory : HistoryBehavior.Default;

    App.NavigationService.NavigateTo("SecondPage", arguments.Text, 
        historyBehavior);
}

We simply get the HistoryBehavior based on Switch value and then we pass it along with the target page and the arguments to the NavigateTo method. Finally, in the Second Page we override the OnAppearing method:

protected override void OnAppearing()
{
    var args = this.GetNavigationArgs();
    passedArguments.Text = $"Passed arguments: {args}";

    base.OnAppearing();
}

Using the GetNavigationArgs extension method, at line 3 we retrieve the arguments we have specified with NavigateTo.

Another interesting point about this solution is that it can easily be integrated with an MVVM approach, but we’ll go deeper in this argument in a future post.

You can download the complete example using the link below:
A simple NavigationService for Xamarin.Forms

Categories: C#, Xamarin
  1. No comments yet.
  1. 11/07/2016 at 14:51
Comments are closed.
%d bloggers like this: