Xamarin.Forms Shell navigation outside the visual hierarchy - c#

I'm having a problem with how to implement navigation to pages that are not represented in shell visual hierarchy (defined in my Shell xaml).
From what I read about Shell navigation in the docs, there are two ways I can navigate to such a page:
Using the Navigation property:
Navigation.PushAsync(new TargetPage());
Registering a route and using Shell's URI navigation:
Routing.RegisterRoute("targetPageRoute", typeof(TargetPage));
Shell.Current.GoToAsync("targetPageRoute");
Both methods encounter the same problem: Once you use either method to navigate to a page outside the visual hierarchy, normal navigation between Shell's flyoutItems (using the flyout menu) will crash the app with the error:
System.Collections.Generic.KeyNotFoundException: The given key 'MyProject.TargetPage' was not present in the dictionary.
How to reproduce:
Add two items to Shell's visual hierarchy:
<FlyoutItem Title="page 1">
<Tab>
<ShellContent>
<local:Page1 />
</ShellContent>
</Tab>
</FlyoutItem>
<FlyoutItem Title="page 2">
<Tab>
<ShellContent>
<local:Page2 />
</ShellContent>
</Tab>
</FlyoutItem>
Use a button on Page1 to navigate to Page3 (a page not defined above) using either of the two ways to navigate described at the top of this post:
private void Button_Clicked(object sender, EventArgs e) {
Navigation.PushAsync(new Page3());
}
Use the flyout menu to navigate to Page2
Use the flyout menu to navigate to Page1 - the app should now crash.
I have tested this in my main project and in a small test project extensively and cant seem to find a solution. Any help would be greatly appreciated.

Here's the same issue from xamarin forms' github: https://github.com/xamarin/Xamarin.Forms/issues/6738
Also, if you scroll down, you'll see pull request, which actually resolves the problem (already helped our company's application).
You have to implement a custom renderer, which is going to inherit from ShellItemRenderer, and override the existing HandleFragmentUpdate (because of the fact, that it uses private fields from the original ShellItemRendererBase, you'll have to rebase them here too (not override, just copy them from current xamarin android ShellItemRendererBase.cs file)).
BUT, as the official docs suggest, you shall not just assign this renderer to a ShellItem derivative, instead, you'll have to create custom ShellRenderer with overriding it's CreateShellItemRenderer method (so it would create your fixed shell item renderer instead of the default one). You just apply this renderer to a custom shell control in xamarin forms.
Of course, everything you do now is temporary, until xamarin pushes new update with this fix included...

you could try to change the shell.xaml like this:
<FlyoutItem FlyoutDisplayOptions="AsMultipleItems">
<Tab Title="page 1">
<ShellContent >
<local:Page1 />
</ShellContent>
</Tab>
<Tab Title="page 2">
<ShellContent >
<local:Page2 />
</ShellContent>
</Tab>
</FlyoutItem>

Have you tried to add your Route before setting the BindingContext ?
For Example :
public NavigationShell()
{
Routing.RegisterRoute("targetPageRoute", typeof(TargetPage));
BindingContext = this;
}

Related

.NET MAUI Shell: How can `ShellContent` page be added to the navigation stack to enable "navigate back" behavior?

I have several page in MAUI Shell XAML:
<ShellContent
Title="Dashboard"
ContentTemplate="{DataTemplate local:MainPage}"
Route="MainPage">
<ShellContent.Icon>
<FontImageSource Glyph="" FontFamily="AwesomeSolid" Color="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource Gray100}}" />
</ShellContent.Icon>
</ShellContent>
<ShellContent
Title="Ot"
ContentTemplate="{DataTemplate local:OtherPage}"
Route="OtherPage">
<ShellContent.Icon>
<FontImageSource Glyph="" FontFamily="AwesomeSolid" Color="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource Gray100}}" />
</ShellContent.Icon>
</ShellContent>
Shell navigation works fine, but if user taps "back" on OtherPage the application will not return the user back to the main page. Also, navigation from Shell Flyout does not add a back button to the OtherPage.
If I put a button on OtherPage and try to explore navigation stack it seems like it doesn't record previous page:
private async void Button_Clicked(object sender, EventArgs e)
{
Console.WriteLine(Shell.Current.Navigation.NavigationStack.Count); // prints 1
await Shell.Current.Navigation.PopAsync(); // doesn't work
await Shell.Current.GoToAsync(".."); // doesn't work
await Shell.Current.GoToAsync("///MainPage"); // navigates to MainPage
}
From the last call I can see that navigation is working, but navigating from Shell doesn't add page to the navigation stack.
How do I achieve the behavior that:
Application starts on MainPage
User navigates to OtherPage via Shell Flyout
User taps "back" button while on OtherPage and returns to MainPage
The shell content in the Shell.Xaml will be default set as the root page in the navigation stack. In other words, when you navigates to a page via Shell Flyout, that page will be a rootpage.
And the back button will show only when you go to a page which is not added into the Shell as a flyoutitem. So both of the OtherPage and the MainPage will not show the back button when you navigation between the two pages. You navigate between the two pages with the Shell.Current.GoToAsync is same as using the Shell Flyout.
So you can remove one of the ShellContent and only use the Shell.Current.GoToAsync to navigate between the two page or only use the Shell Flyout.
Update
In the OtherPage's xaml.cs:
protected override bool OnBackButtonPressed()
{
await Shell.Current.GoToAsync("///MainPage");
return true;
}

ShellContent tag in .net maui not working

trying to make a flyout menu in .net maui, from what i found out i have to use App.xaml for it, it does not seem to be working, here is the code in App.xaml
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:NSM_Maui"
x:Class="NSM_Maui.App">
<Application.MainPage>
<Shell FlyoutBehavior="Flyout" FlyoutHeaderBehavior="Fixed" FlyoutVerticalScrollMode="Auto">
<FlyoutItem Title="Home" >
<ShellContent ContentTemplate="{DataTemplate local:HomePage}"/>
</FlyoutItem>
</Shell>
</Application.MainPage>
it work fine up to the but the does not seem to be working, the Datatemplate string is a ContentPage xaml file that i made its called "HomePage" i was also following this videoenter image description here https://youtu.be/KUzpFrReJNQ
i fixed it, iam not really sure what i changed but i had to make a new project and in the AppShell.xaml there is the tag and you have to add Shell.FlyoutBehavior="Flyout" and also add the <ShellContent> tag <ShellContent>

Defining layout of a crossplatform mobile app in c# (xamarin forms) using Shell

I am new to xamarin forms and shell and I am currently trying to design the hierarchial structure of an app using Shell (Xamarin.Forms Shell). I want to design my app so that once the user logs in, he will be taken to a page (let's call it home page one) that can be chosen from the flyout menu and has it's own unique tabs at the bottom. I also want there to be another page visible in the flyout menu (let's call it home page two) so that if the user clicks on home page two, it is taken to home page two, which has it's own different tabs at the bottom. The user should see different tabs depending on which home page they chose in the flyout menu.
Here's the idea of the overall layout:
Once user starts up application, they are taken to a start page (StartPage)
The start page gives the user the choice to login (LoginPage) or register (RegistrationPage)
Once the user successfully logs in, they are taken to home page one(HomePageOne) which contains 4 tabs at the bottom
If the user opens the flyout menu, it should display an item for HomePageOne and HomePageTwo, as well as a logout menu item.
If the user clicks on home page two (HomePageTwo), they are taken to home page two, which has different tabs from home page one and vice versa
If the user chooses to logout, they are taken back to the start page (StartPage)
I currently have two questions. First, I'm not sure how to define my shell to account for the different home pages having different tabs. Second, when the user chooses to logout, it is still displaying the flyout menu (hamburger menu) even though that should only be visible while the user is successfully logged in.
Here is my AppShell.xaml:
<ShellItem Route="Start" FlyoutItemIsVisible="False">
<ShellContent Route="StartPage" ContentTemplate="{DataTemplate local:StartPage}" />
</ShellItem>
<ShellItem Route="Login" FlyoutItemIsVisible="False" >
<ShellContent Route="LoginPage" ContentTemplate="{DataTemplate local:LoginPage}" />
</ShellItem>
<ShellItem Route="Registration" FlyoutItemIsVisible="False" >
<ShellContent Route="RegistrationPage" ContentTemplate="{DataTemplate local:RegistrationPage}" />
</ShellItem>
<FlyoutItem Title="Home Page One" Icon="icon_about.png">
<ShellContent Route="HomePageOne" ContentTemplate="{DataTemplate local:HomePageOne}" />
</FlyoutItem>
<FlyoutItem Title="Home Page Two" Icon="icon_feed.png">
<ShellContent Route="HomePageTwo" ContentTemplate="{DataTemplate local:HomePageTwo}" />
</FlyoutItem>
<!-- When the Flyout is visible this will be a menu item you can tie a click behavior to -->
<MenuItem Text="Logout" StyleClass="MenuItemLayoutStyle" Clicked="MenuLogout_Clicked">
</MenuItem>
Here is my AppShell.xaml.cs:
public partial class AppShell : Xamarin.Forms.Shell
{
public AppShell()
{
InitializeComponent();
Routing.RegisterRoute(nameof(StartPage), typeof(StartPage));
Routing.RegisterRoute(nameof(RegistrationPage), typeof(RegistrationPage));
Routing.RegisterRoute(nameof(LoginPage), typeof(LoginPage));
}
private async void MenuLogout_Clicked(object sender, EventArgs e)
{
await Shell.Current.GoToAsync("StartPage");
}
}
I found the answer by watching a couple of videos by James Montemagno on youtube. If you are new to xamarin, I highly recommend these videos!
https://www.youtube.com/watch?v=ylbgWHB_gMI&t=1s - Login flow
https://www.youtube.com/watch?v=8iYpLMKE_ws&t=1067s - Shell Navigation

PopToRootAsync to a selected Shell Flyout Item

I have this code below to create an action bar with an integrated side bar using <Shell> so the user can navigate easily by swiping right. Being new to C#, I understand that the first ShellContent is gonna be the page to be loaded and previewed by the <Shell> when the app opens which is ContentTemplate="{DataTemplate local:LoanHistory}"
I also understand when you click on the FlyoutItems you aren't gonna add stack to the navigation but the <Shell> is changing the contents each time you click on it. This code below is in a file called MainPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<Shell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:LoanApp2.Views"
mc:Ignorable="d"
x:Class="LoanApp2.MainPage" BackgroundColor="#62bef0" Title="Dylan">
<FlyoutItem Title="MyTabApp" Shell.TabBarIsVisible="False" FlyoutDisplayOptions="AsMultipleItems">
<ShellContent Title="Loan History" IsTabStop="False" ContentTemplate="{DataTemplate local:LoanHistory}"/>
<ShellContent Title="Apply for Loan" IsTabStop="False" ContentTemplate="{DataTemplate local:LoanApplication}"/>
<ShellContent Title="Logout" IsTabStop="False" ContentTemplate="{DataTemplate local:LoanHistory}"/>
</FlyoutItem>
</Shell>
So, via the side bar (shell) - I click on Apply for Loan which takes me to LoanApplication.xaml. By this time, no stack is added to the navigation. In the LoanApplication.xaml, I'm using PushAsync binded to a button to take me to a page called AmountLoanable.xaml. This gets added to the stack and the back button on the action bar is now available.
After I am done with the stuff on AmountLoanable.xaml, I use PopToRootAsync to remove the navigation stack except for the root page (which is the <Shell>, MainPage.xaml). However, it takes me back to the "Apply for Loan" which was selected prior, what I want to happen is for it to load a fresh new <Shell>, MainPage.xaml page. In that way, the first page (Loan History) will be the one selected instead of the last page accessed via <Shell>
try to reset your Application.Current.MainPage to your default Shell ex: Application.Current.MainPage = new AppShell();

Hiding a tab in Xamarin.Forms using Prism framework

I am working on a Xamarin Forms project that uses an MVVM pattern and Prism.
My issue is, I need to be able to hide a tab on a tab page based off of a bool. There is a bindable property in the XAML called "IsVisible", which I assumed would hide the tab from the user, but instead the tab still shows but displays a blank page when selected.
Currently my XAML looks like this
<?xml version="1.0" encoding="utf-8"?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
xmlns:local="clr-namespace:com.XXXXXXX"
prism:ViewModelLocator.AutowireViewModel="True"
BackgroundColor="White"
x:Class="com.XXXXXXXXXX.EditChargePage"
Title="{Binding Title}">
<local:EditChargeDetailsPage Title="Details" />
<local:EditChargeTrackingPage Title="Tracking" IsVisible="{Binding TabContext.HasTracking}" IsEnabled="{Binding TabContext.HasTracking}"/>
<local:EditChargeNotesPage Title="Notes" />
</TabbedPage>
Originally to solve the problem I just used "IsVisible", when that didn't work as expected I added "IsEnabled" which didn't seem to do anything.
Is there a way to hide the Tab from the UI using a boolean value in Xamarin (or Prism for Xamarin) without breaking MVVM?
You might want to check out the Prism Samples. The TabbedNavigation sample demonstrates how to initialize your tabbed children with INavigatingAware or the IEventAggregator, as well as dynamically adding the tabs at run time. If you need even finer control, such as a value in a ViewModel of one of your Child Pages that determines whether some other page should be shown, then you could use this to publish an event with the IEventAggregator and subscribe to that event in your TabbedPage to handle adding or removing the page.
Unfortunately IsVisible and IsEnabled do not actually work within a TabbedPage the way you were hoping. So the only way you can accomplish this is to actually manipulate the Children of the TabbedPage directly pushing or popping them from the collection.

Categories