Global routes currently cannot be the only page on the stack
Asked Answered
S

1

10

Getting the flyout menu to work in Xamarin Forms the way I want it to has been a major pain. I'm trying to get a certain menu item (My Stats) to go to a certain page if the user is logged in, or to a different page if the user is not (The login page). I have attempted two different approaches with no success.

Approach 1

First I tried setting the flyout Item in XAML with specified route in Shell Content like so:

<FlyoutItem Title="My Stats" Icon="icon_feed.png">
        <ShellContent x:Name="shellContent_myStats" Route="MyStatsPage" ContentTemplate="{DataTemplate local:MyStatsPage}" />
</FlyoutItem>

Then I would modify the route programmtically when the user logs in/out:

//Logging Out
            shellContent_myStats.ContentTemplate = new DataTemplate(typeof(LoginPage));
            Routing.SetRoute(shellContent_myStats, "LoginPage");
...
//Logging In
            shellContent_myStats.ContentTemplate = new DataTemplate(typeof(MyStatsPage));
            Routing.SetRoute(shellContent_myStats, "MyStatsPage");

I could not get this to work. For whatever reason, only the first piece to get executed would take effect. For example, if I started the app while logged out, it would direct to the login page as expected but would not switch over once I log out. The reverse is true as well if I start the app while logged in, it navigates to the "My Stats" page as expected, but will continue to do so even after I log out.

I have tried a few different variations of the above code to no avail.


Approach 2

The second approach I tried, was instead specifying a menu item in XAML with an OnClick event handler like so:

<MenuItem Text="My Journey" StyleClass="MenuItemLayoutStyle" Clicked="OnMyStatsClicked"/>

Code behind:

public AppShell()
{
    //Route now needs to be registered in page constructor
    Routing.RegisterRoute(nameof(MyStatsPage), typeof(MyStatsPage));
    ...

public async void OnMyStatsClicked(object sender, EventArgs e)
{
    Shell.Current.FlyoutIsPresented = false;
    if (LoggedIn)
    {
        // Prefixing with `//` switches to a different navigation stack instead of pushing to the active one
        await Shell.Current.GoToAsync($"//{nameof(MyStatsPage)}");
    }
    else
    {
        await Shell.Current.GoToAsync($"//{nameof(LoginPage)}");
    }
}
...

Now when I select the menu item with this setup (while logged in), it throws the following error:

System.Exception: 'Global routes currently cannot be the only page on the stack, so absolute routing to global routes is not supported. For now, just navigate to: MyStatsPage'

If I follow the suggestion given by removing the two slashes in the route ("//"), the navigation works, but then the flyout menu is not available in the top left and is replaced with a back button. This is not desired, I want the menu to still be available. Why can't I use absolute routing on a route defined in the code behind..?

I appreciate any suggestions on either of the two methods mentioned to get this to work. Been at it for days trying to do something seemingly so simple.. Thanks

Stationmaster answered 14/4, 2021 at 20:37 Comment(1)
Im getting the same issue did u ever find the solution for this it dont appear to be fixed even now.Ribble
C
13

You can set up a trick using the property FlyoutItemIsVisible, the ShellContent (thus the related page) will be registered in the Shell hierarchy but won't be visible in the flyout, also it will be loaded only when it is needed since we are using ContentTemplate.

Assuming you are only using the flyout without the bottom tabs:

AppShell.xaml

<Shell Shell.TabBarIsVisible="False"
...

  <FlyoutItem>
        <ShellContent ContentTemplate="{DataTemplate local:MainPage}"/>
        <ShellContent Route="AnotherPage"
                      FlyoutItemIsVisible="False"
                      ContentTemplate="{DataTemplate local:AnotherPage}"/>

        <ShellContent Route="LoginPage"
                      FlyoutItemIsVisible="False"
                      ContentTemplate="{DataTemplate local:LoginPage}"/>
    </FlyoutItem>


    <MenuItem Text="My Stats Page"
              Clicked="MenuItem_Clicked"/>

AppShell.xaml.cs

async void MenuItem_Clicked(object sender, System.EventArgs e)
{
 if (LoggedIn)
    await Shell.Current.GoToAsync($"//{nameof(AnotherPage)}");
  else
    await Shell.Current.GoToAsync($"//{nameof(LoginPage)}");
}

Note

In the case you are using bottom tabs at the same time, or as another approach I suggest to hide unrelated pages to the current log status (example: hide LoginPage if the user is logged in).

How can you restrict/control the navigation routes the user can visit based on login status/role?

Cathryncathy answered 14/4, 2021 at 21:13 Comment(3)
That did it! I thought I tried this, but it turns out I was using the "IsVisible" property. That wouldn't work because when IsVisible = false, the route seems to also get unregistered, but using "FlyoutItemIsVisible" preserves the route, thanks so much!Stationmaster
Thanks, this trick also worked for a Maui shell app and is much simpler than having to worry about figuring out which route prefix to use, e.g. ("/", "//", or "///"). Now I just use "///" for all routing, for example, to route to any page I simply use await Shell.Current.GoToAsync($"///{nameof(AboutPage)}");Kao
what does the /// do to the navigation? I used // but anytime I submit a form and navigate to another page, I still see my data anytime I navigate to that first page except I close the appToadinthehole

© 2022 - 2024 — McMap. All rights reserved.