1
votes

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)

2
Same behaviour with the official sample projectShaw
Hi, it seems to be a normal phenomenon. When switching flyout item, it will not change the stack order of each item. It needs user to operate it manually. But the second times, maybe the shell will clear the stack.Junior Jiang
In addition, if using Shell.Current.GoToAsync, the next page will not show the FlyoutIcon menu. Therefore, from PageC, you could share the code of how to use the flyout menu to navigate to History.Junior Jiang
@Craig However, I could not find a way to override the click event of flayout item. Maybe you could submit a feature request in Github here. Then there will be engineers from Microsoft to know the needs. If they accept this feature request, they will update this in the next version of xamrin forms. If you have submitted it, remember to share the link here. I also will follow it up there.Junior Jiang
@JuniorJiang-MSFT Yeah sure, I'll submit a feature request for this and I'll share the link when it is done. Thank you for your help.Craig

2 Answers

0
votes

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

0
votes

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]);
                }
            }
        }
    }
}