Xamarin Forms Shell - Navigation to old navigation stack rather than flyout page

Viewed 2283

I’m having trouble with my Xamarin Forms Shell app. I'm not sure if this is a bug or expected behaviour, can someone point me in the right direction.

I have an app with 2 pages in the Visual Shell Hierarchy:

Search & History

<FlyoutItem Title="Search" Icon="search.png">
    <Tab Title="Search">
        <ShellContent Route="searchpage">
            <views:SearchPage />
        </ShellContent>
    </Tab>
</FlyoutItem>
<FlyoutItem Title="History" Icon="history.png">
    <Tab Title="History">
        <ShellContent>
            <views:HistoryPage />
        </ShellContent>
    </Tab>
</FlyoutItem>

And several pages (let’s call them PageA, PageB and PageC) registered like so:

Routing.RegisterRoute("PageA", typeof(PageA));    
Routing.RegisterRoute("PageB", typeof(PageB));    
Routing.RegisterRoute("PageC", typeof(PageC)); (Oops, I should probably use nameof here)

Anyway, I start on the Search page and navigate to PageA like so:

Shell.Current.GoToAsync("PageA");

Because PageA is not in the visual hierarchy, this gives me a navigation stack like so:

"//searchpage/PageA"

Using the same relative navigation approach, I navigate to PageB then PageC, so my navigation stack is like so:

"//searchpage/PageA/PageB/PageC"

From PageC, I use the flyout menu to navigate to History, the history page opens fine.

Now on the history page, I use the flyout menu again and click the Search tab

But I'm not taken to the search page as expected, I'm taken back to PageC (taken back to my previous navigation stack).

From this page (PageC) if I use the flyout menu again and click the Search tab, it navigates correctly to the Search page.

How should this work and how can I stop it navigating to PageC when I select the Search tab from the flyout?

Thanks

(ps - I'm using Xamarin Forms 4.7 at present)

4 Answers

For those still looking for answers, an answer by Damir's Corner

await Shell.Current.GoToAsync($"///{tag}", false); //Checks the Navigation Stack Downward

The code above checks the current Navigation Stack, and searches for the Route Tag Downward (or inside the Navigation Stack) and then replaces the entire Stack with the Found Route.

Hope this helps for those looking for answers.

When jumping between different tabs (and by doing so different navigationstacks), Williams solution (Damir's Corner) worked for me.

My scenario:

  • Tab1 -> ListPage -> DetailsPage
  • Tab2 -> CreateStep1Page -> CreateStep2Page

After saving the entry in CreateSpecificPage, I navigate two times:

await Shell.Current.GoToAsync($"///{nameof(CreateStep1Page)}", false);
await Shell.Current.GoToAsync($"///{nameof(ListPage)}/{nameof(DetailsPage)}", true);

I will be raising this as a feature request when I get 5 minutes, but I have found a solution to this issue.

  1. I made the last page (PageC) a Root page and removed it from my Routes Script. I did this by adding the following to the AppShell.Xaml like so:

    <ShellItem Route="PageC">
        <ShellContent ContentTemplate="{DataTemplate views:PageC}" />
    </ShellItem>
    
    and removing this code:
    Routing.RegisterRoute("PageC", typeof(PageC));
    
  2. Now when navigating to PageC, I am no longer pushing onto the stack with a relative navigation, I am now navigating to a root page like so:

     await Shell.Current.GoToAsync("//PageC");
    
  3. Before we navigate to PageC, we need to clear the current navigation stack. I tried PopToRootAsync, but this showed the SearchPage before navigating to PageC. I found the following code works:

         // FYI - Navigation Stack: //SearchPage/PageA/PageB
    
         var pageB = Shell.Current.Navigation.NavigationStack[2];
         var pageA = Shell.Current.Navigation.NavigationStack[1];
    
         Shell.Current.Navigation.RemovePage(pageB);
         Shell.Current.Navigation.RemovePage(pageA);
    
         // Now navigate to our route page, PageC.
         await Shell.Current.GoToAsync("//PageC");
    

I will be using a slightly more elegant way of getting the pages rather than hard coding the index, but thought this was the easiest way to demonstrate what was happening.

The next time I navigate to the SearchPage now, I get the SearchPage

I think this feature is intentional. One thing to understand is that as mentioned in the docs, every Tab contains separate navigation stack, Shell.Current.Navigation points to the currently active/visible Tab navigation stack. So what you can do is actually quite simple.

You just override Shell.OnNavigating event, and from remove every page except root one from the current navigation stack only if you are navigating between different route trees.

Like, from //route1/page1 to //route2/page2.

You can adapt following code per your use case.

public partial class AppShell : Xamarin.Forms.Shell
{
    public AppShell()
    {
        InitializeComponent();

        Setup.RegisterRoutes(this);
    }

    protected override void OnNavigating(ShellNavigatingEventArgs args)
    {
        base.OnNavigating(args);

        if (args.Current != null && args.Target != null && args.Current.Location.OriginalString.StartsWith("//") && args.Target.Location.OriginalString.StartsWith("//")) {
            var currentRoot = args.Current.Location.OriginalString.TrimStart('/').Split('/').First();
            var targetRoot = args.Target.Location.OriginalString.TrimStart('/').Split('/').First();
            
            // we are navigating between tabs
            if (!string.Equals(currentRoot, targetRoot, StringComparison.OrdinalIgnoreCase) && Navigation.NavigationStack.Count > 1) {
                for (var i = Navigation.NavigationStack.Count - 1; i > 0; i--) {
                    Navigation.RemovePage(Navigation.NavigationStack[i]);
                }
            }
        }
    }
}
Related