Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GoBackTo without popping each page #2818

Closed
berhir opened this issue Jan 20, 2023 · 9 comments · Fixed by #3048
Closed

GoBackTo without popping each page #2818

berhir opened this issue Jan 20, 2023 · 9 comments · Fixed by #3048

Comments

@berhir
Copy link
Contributor

berhir commented Jan 20, 2023

When I have a navigation stack PageA/PageB/PageC/PageD/PageE and want to go back to PageB directly using GoBackTo, I don't want to see PageC and PageD for a short moment.

In the code of the PageNavigationService I see that GoBackTo calls DoPop for each page until we reach the target page (in my sample PageB). This causes flickering because each page is visible for a short moment.

In contrast, GoBackToRoot works differently and does not pop each page. I understand that GoBackToRoot works only within a navigation page and does not work with modal navigation.
But it would be great to get the same behavior in GoBackTo as long as the necessary requirements are met. I am not sure if it's easy to automatically detect if it's possible or not. I can also live with an optional parameter to enforce it and an exception if it doesn't work.

Or is there another way to achieve what I am trying to do?

@dansiegel
Copy link
Member

This has been on the backlog for a while but is a feature I'd like to get in for sure

@dansiegel dansiegel transferred this issue from PrismLibrary/Prism.Maui Feb 16, 2023
@AmrAlSayed0
Copy link
Contributor

This is an extension method that I have already written because I needed it in my project.

using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

using Prism.Common;
using Prism.Navigation;

namespace Core.FE.XAM.Extensions
{
    public static class NavigationServiceExtensions
    {
        #region StaticFields
        static MethodInfo? _getNavigationParametersInternalMethodInfo;
        #endregion

        #region StaticMethods
        [return: NotNull]
        public static async Task<INavigationResult> GoBackToRouteAsync([DisallowNull] this INavigationService navigationService, [DisallowNull] string routeGoBackTo, [AllowNull] string? routeToPushAfter = null, [AllowNull] INavigationParameters? parameters = null, bool isAnimated = true)
        {
            string navigationUriPath = navigationService.GetNavigationUriPath();
            string[] pageSegments = navigationUriPath.Split("/", StringSplitOptions.RemoveEmptyEntries)
                                                     .Where(pageSegment => !string.IsNullOrWhiteSpace(pageSegment))
                                                     .ToArray();
            int indexOfPageToPopTo = pageSegments.Select((value, i) => new
                                                                       {
                                                                           Index = i,
                                                                           Value = value
                                                                       })
                                                 .FirstOrDefault(v => string.Equals(v.Value, routeGoBackTo, StringComparison.Ordinal))
                                                ?.Index ??
                                     -1;
            if (indexOfPageToPopTo <= -1)
                return new NavigationResult
                       {
                           Success = false,
                           Exception = new NavigationException($"Cannot find page \"{routeGoBackTo}\" on the stack.", (navigationService as IPageAware)?.Page)
                       };

            int numOfSegmentsToPop = pageSegments.Length - 1 - indexOfPageToPopTo;
            string route = string.Concat(Enumerable.Repeat("../", numOfSegmentsToPop));

            if (!string.IsNullOrWhiteSpace(routeToPushAfter))
            {
                route += routeToPushAfter;
            }
            else
            {
                parameters ??= new NavigationParameters();
                _getNavigationParametersInternalMethodInfo ??= typeof(NavigationParametersExtensions).GetMethod("GetNavigationParametersInternal", BindingFlags.NonPublic | BindingFlags.Static)!;
                // ReSharper disable once SimplifyConditionalTernaryExpression
                // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
                if (_getNavigationParametersInternalMethodInfo != null)
                {
                    if (_getNavigationParametersInternalMethodInfo.Invoke(null,
                                                                          new object[]
                                                                          {
                                                                              parameters
                                                                          }) is INavigationParametersInternal npi)
                    {
                        if (!npi.ContainsKey("__NavigationMode")) npi.Add("__NavigationMode", NavigationMode.Back);
                    }
                    else
                    {
                        parameters.Add("__NavigationMode", NavigationMode.Back);
                    }
                }
                else
                {
                    parameters.Add("__NavigationMode", NavigationMode.Back);
                }
            }

            return parameters is null
                   ? await navigationService.NavigateAsync(route, animated: isAnimated)
                   : await navigationService.NavigateAsync(route, parameters, animated: isAnimated);
        }
        #endregion
    }
}

@brianlagunas
Copy link
Member

In this specific case, you should be able to use NavigateAsync("../../../") to navigate back to ViewB without seeing anything being popped. Granted, this is not intuitive, but it should unblock you until we can discuss a better API.

@SarthakB26
Copy link

SarthakB26 commented Feb 21, 2023

@brianlagunas
Copy link
Member

That's because The Xamarin team stole took inspiration from Prism's navigation 😄

@dansiegel
Copy link
Member

That's because The Xamarin team stole took inspiration from Prism's navigation 😄

And then promptly realized that they couldn't do everything Prism could so they watered it down and flat out architected parameters wrong.

@dansiegel dansiegel added the MAUI label Feb 21, 2023
@dansiegel
Copy link
Member

I'm adding the MAUI label to this to be clear Prism.Forms is in maintenance mode. We may add some small things for Prism 9.0 but in general we will not be making changes like this in Prism.Forms for v9 as Xamarin.Forms is going to be EOL soon.

The APIs that are currently being considered following my conversation with @brianlagunas are as follows (NOTE each will also have a version without INavigationParameters):

  • Task INavigationService.GoBackAsync(string viewName, INavigationParameters parameters);
  • Task INavigationService.NavigateFromAsync(string viewName, Uri route, INavigationParameters);

The goal here is that this makes it easier / more discoverable but it should ultimately still function the same as ../../someRoute. We also want to be sure that this simplifies scenarios such as:

  • FlyoutPage/NavigationPage/ViewA navigating from ViewA without recreating the Flyout to navigate like NavigateFromAsync("FlyoutPage", "NavigationPage/ViewB") with a final stack like FlyoutPage/NavigationPage/ViewB

  • There is also likely to be some limitations like if you have ViewA/ViewA/ViewB/ViewC and you have GoBackAsync("ViewA") which instance of ViewA should be the correct one to navigate to.

@niimima
Copy link
Contributor

niimima commented Nov 17, 2023

Hello, I've been looking into this issue and I'm interested in tackling it. While going through the code, it seems that the unit tests for the PageNavigationServiceFixture class, which implements INavigationService in charge of realizing PageNavigationService, cannot be executed. If it's not just an issue with my local environment, I need to fix unit tests for this class. Would it be okay for me to attempt to address this issue?

@dansiegel
Copy link
Member

@niimima actually I totally forgot that was set to None Include... I will need to update that to work with the changes to the PageNavigationService in Maui from Xamarin.Forms. Thanks for calling that out.

In the mean time yes please feel free to pick up the feature for GoBackTo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants