4

I want the user to be able to move products from one location to another. When done, I want to go back to the products page because he's likely to keep moving products from the same location. I've got two slightly different scenarios.

Scenario 1:

//SourceAisle/SourceLocation/Products/TargetAisle/TargetLocation

Scenario 2:

//SourceAisle/SourceLocation/Products/FavoriteLocation

In both scenarios when the user is done I want to go back to

//SourceAisle/SourceLocation/Products

I could do Shell.Current.GoToAsync("../..") in scenario 1 and Shell.Current.GoToAsync("..") in scenario 2 but I'd like to have some shared logic ie go back to products page.

According to the documentation it's possible to search backwards using either /route or ///route but I'm not sure I understand how it works. I don't want to "push" any new page, I basically want to "pop" all pages up to the page I want to be displayed.

Is it possible using Shell navigation?

NOTES

I had created an extension methods in an older Xamarin project that does exactly the above but I'd prefer not to use it if the Shell provides a native way to do so.

// await Shell.Current.Navigation.PopToAsync<ProductsPage>();

internal static class NavigationExtensions
{
    private static readonly SemaphoreSlim _semaphore = new (1, 1);

    public static async Task PopToAsync<T>(this INavigation nav, Page nextPage = null, bool animated = true) where T : Page
    {
        await _semaphore.WaitAsync();

        try
        {
            bool exists = nav.NavigationStack
                .Where((p, i) => i <= nav.NavigationStack.Count - 2)
                .Any(p => p is T);

            if (!exists)
                throw new ArgumentException($"The specified page {typeof(T).Name} does not exist in the stack");

            for (var index = nav.NavigationStack.Count - 2; index > 0; index--)
            {
                if (nav.NavigationStack[index] is T)
                    break;

                nav.RemovePage(nav.NavigationStack[index]);
            }

            if (nextPage is not null)
                nav.InsertPageBefore(nextPage, nav.NavigationStack[nav.NavigationStack.Count - 1]);

            await nav.PopAsync(animated);
        }
        finally
        {
            _semaphore.Release();
        }
    }
}

EDIT

AppShell.xaml

<?xml version="1.0" encoding="UTF-8" ?>
<Shell
    x:Class="MyApp.AppShell"
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:views="clr-namespace:MyApp.Views">

    <FlyoutItem Route="SourceAisle" Title="Stock Movement">
        <ShellContent ContentTemplate="{DataTemplate views:AislesPage}" />
    </FlyoutItem>
</Shell>

AppShell.xaml.cs

public partial class AppShell : Shell
{
    public AppShell()
    {
        InitializeComponent();

        Routing.RegisterRoute("SourceAisle", typeof(AislesPage));
        Routing.RegisterRoute("SourceLocation", typeof(LocationsPage));
        Routing.RegisterRoute("Products", typeof(ProductsPage));
        Routing.RegisterRoute("TargetAisle", typeof(AislesPage));
        Routing.RegisterRoute("TargetLocation", typeof(LocationsPage));
        Routing.RegisterRoute("FavoriteLocation", typeof(FavoritesPage));
    }
}

The aisle and location pages are used for both source and target and have some state to deal with the two different cases but that's out of the scope of this question.

From LocationsViewModel I call await Shell.Current.GoToAsync("Products"), from ProductsViewModel I call await Shell.Current.GoToAsync("TargetAisle") and so on. I think this is what's called relative routing and not absolute, not sure if I should do things differently.

Arthur Rey
  • 2,990
  • 3
  • 19
  • 42
  • Not sure why someone voted to close this question with "Needs more focus". I think it's just one question. If it's unclear, all I'm asking is how to go back to any page from any page using Shell. Given routes `//A/B/X` and `//A/B/Y/Z` I want to know if there exists a way to go back to `//A/B`. – Arthur Rey Jan 30 '23 at 13:13
  • If //A/B is absolute route, it should work. The question here is do you have a ShellContent, that has route set to B, and is inside something, that has route set to A. To be honest your Shell XAML will be helpful. (And if those are missing, the routes registered in your Shell subclass.) – H.A.H. Jan 30 '23 at 15:06
  • 1
    I have seen your edit. What is stopping you from calling Shell.Current.GoToAsync("//A/B")? – H.A.H. Jan 30 '23 at 18:48
  • In [Shell navigation / Relative routes](https://learn.microsoft.com/en-us/dotnet/maui/fundamentals/shell/navigation?view=net-maui-7.0#relative-routes), read the description of `//route`: *"... The matching page will **replace** the navigation stack."* If you don't want to keep pushing more pages, add `//` in front. – ToolmakerSteve Jan 30 '23 at 20:10
  • @H.A.H. My B view model has a *QueryProperty* that is passed from A (so from A I would call something like `Shell.Current.GoToAsync("B?id=10")`). Calling `Shell.Current.GoToAsync("//A/B")` from X or Z I indeed go to page B but its *QueryProperty* is null. I guess it makes sense because I'm replacing the navigation stack. Calling `Shell.Current.GoToAsync("..")` or `Shell.Current.GoToAsync("../..")` works because I'm going back to an existing page on the stack. What I want may not exist, but I'd like to turn the *go back 1 or 2 pages* logic to *go back to B*. – Arthur Rey Jan 31 '23 at 11:06
  • Arthur, if you check PopToRoot code in the MAUI code itself, you will see something like: for (int i = _currentStack.Count; i > 1; i--) cycle, and then removing pages. You can check even specific platform renderers, to see how it is done. The code is commented, you get what is happening right away. You want to go //A/b go back and leave 2 pages in the stack. Make a method, that accepts how much to leave as parameter, and call it, if you want this to be re-usable. That is my point of view. Just want to point out, I am using this: Shell.Current.GoToAsync("..") and I am happy with it. – H.A.H. Jan 31 '23 at 12:41

2 Answers2

0

You can write a command like this which can recognize the page name then navigate to the target page.

 public class MyViewModel: ContentPage
    {
        public ICommand NavigateCommand { get; private set; }
        public MyViewModel()
        {
            NavigateCommand = new Command<Type>(
             async (Type pageType) =>
             {
                 Page page = (Page)Activator.CreateInstance(pageType);
                 await Navigation.PushAsync(page);
             });
        }
    }

Here is the style of the command.

<TextCell Text="Entry"
          Command="{Binding NavigateCommand}"
          CommandParameter="{x:Type views:CustomizeSpecificEntryPage}" />
Guangyu Bai - MSFT
  • 2,555
  • 1
  • 2
  • 8
0

I think you can pass parameters within Routes, something like this:

string myValue = "ParameterToPass";
await Shell.Current.GoToAsync($"myLittleParameter?name={myValue}");

This is the link of the documentation: https://learn.microsoft.com/en-us/dotnet/maui/fundamentals/shell/navigation?view=net-maui-7.0#pass-data

Jeremy Caney
  • 7,102
  • 69
  • 48
  • 77
Chagoy
  • 14
  • 2