/* * The important thing to know going into reading this method is that it's not possible to * modify the backstack. You can only push and pop to and from the top of the stack. * So if a user uses an API like `RemovePage` or `InsertPage` we will typically ignore processing those here * unless it requires changes to the NavBar (i.e removing the first page with only 2 pages on the stack). * Once the user performs an operation that changes the currently visible page then we process any stack changes * that have occurred. * Let's say the user has pages A,B,C,D on the stack * If they remove Page B and Page C then we don't do anything. Then if the user pushes E onto the stack * we just transform A,B,C,D into A,D,E. * Platform wise that's a "pop" but we use the correct animation for a "push" so visually it looks like a push. * This is also the reason why we aren't using the custom animation features on the navigation component itself. * Because we might be popping but visually pushing. * * The Fragments that are on the stack also do not have a hard connection to the page they originally rendereded. * Whenever a fragment is the "visible" fragment it just figures out what the current page is and displays that. * Fragments are recreated everytime they are pushed on the stack but the handler renderer is not. * It's just attached to a new fragment * */ void ApplyNavigationRequest(NavigationRequest args) { if (IsNavigating) { // This should really never fire for the developer. Our xplat code should be handling waiting for navigation to // complete before requesting another navigation from Core // Maybe some day we'll put a navigation queue into Core? For now we won't throw new InvalidOperationException("Previous Navigation Request is still Processing"); } if (args.NavigationStack.Count == 0) { throw new InvalidOperationException("NavigationStack cannot be empty"); } ActiveRequestedArgs = args; IReadOnlyList <IView> newPageStack = args.NavigationStack; bool animated = args.Animated; var navController = NavHost.NavController; var previousNavigationStack = NavigationStack; var previousNavigationStackCount = previousNavigationStack.Count; bool initialNavigation = NavigationStack.Count == 0; // This updates the graphs public navigation stack property so it's outwardly correct // But we've saved off the previous stack so we can correctly interpret navigation UpdateNavigationStack(newPageStack); // This indicates that this is the first navigation request so we need to initialize the graph if (initialNavigation) { IsInitialNavigation = true; Initialize(args.NavigationStack); return; } // If the new stack isn't changing the visible page or the app bar then we just ignore // the changes because there's no point to applying these to the platform back stack // We only apply changes when the currently visible page changes and/or the appbar // will change (gain a back button) if (newPageStack[newPageStack.Count - 1] == previousNavigationStack[previousNavigationStackCount - 1]) { NavigationFinished(NavigationView); // There's only one page on the stack then we trigger back button visibility logic // so that it can add a back button if it needs to if (previousNavigationStackCount == 1 || newPageStack.Count == 1) { TriggerBackButtonVisibleUpdate(); } return; } // The incoming fragment uses these variables to pick the correct animation for the current // incoming navigation request if (newPageStack[newPageStack.Count - 1] == previousNavigationStack[previousNavigationStackCount - 1]) { IsPopping = null; } else { IsPopping = newPageStack.Count < previousNavigationStackCount; } IsAnimated = animated; var iterator = NavHost.NavController.BackQueue.Iterator(); var fragmentNavDestinations = new List <FragmentNavigator.Destination>(); while (iterator.HasNext) { if (iterator.Next() is NavBackStackEntry nbse && nbse.Destination is FragmentNavigator.Destination nvd) { fragmentNavDestinations.Add(nvd); } } // Current BackStack has less entries then incoming new page stack // This will add Back Stack Entries until the back stack and the new stack // match up if (fragmentNavDestinations.Count < newPageStack.Count) { for (int i = fragmentNavDestinations.Count; i < newPageStack.Count; i++) { var dest = AddFragmentDestination(); navController.Navigate(dest.Id); } } // User just wants to replace the currently visible page but the number // of items in the stack are still the same. // In theory we could just prompt the currently active fragment to swap out the new PageView // But this way we get an animation else if (newPageStack.Count == fragmentNavDestinations.Count) { int lastFragId = fragmentNavDestinations[newPageStack.Count - 1].Id; navController.PopBackStack(); navController.Navigate(lastFragId); } // Our back stack has more entries on it then else { int popToId = fragmentNavDestinations[newPageStack.Count - 1].Id; navController.PopBackStack(popToId, false); } // We only keep destinations around that are on the backstack // This iterates over the new backstack and removes any destinations // that are no longer apart of the back stack var iterateNewStack = NavHost.NavController.BackQueue.Iterator(); int startId = -1; while (iterateNewStack.HasNext) { if (iterateNewStack.Next() is NavBackStackEntry nbse && nbse.Destination is FragmentNavigator.Destination nvd) { fragmentNavDestinations.Remove(nvd); if (startId == -1) { startId = nvd.Id; } } } foreach (var activeDestinations in fragmentNavDestinations) { NavGraph.Remove(activeDestinations); } // If we end up removing the destination that was initially the StartDestination // The Navigation Graph can get really confused if (NavGraph.StartDestination != startId) { NavGraph.StartDestination = startId; } // The NavigationIcon on the toolbar gets set inside the Navigate call so this is the earliest // point in time that we can setup toolbar colors for the incoming page TriggerBackButtonVisibleUpdate(); }