Reputation: 960
My question isn't as simple as the title suggests.. I'm aware that I can use the navigation stack to find out the top-most page and call the navigation event on that page.
This is what I was planning on doing..
Page currentPage;
int index = Application.Current.MainPage.Navigation.NavigationStack.Count - 1;
currentPage = Application.Current.MainPage.Navigation.NavigationStack[index];
//after getting the correct page..
//await currentPage.Navigation.PushAsync(new SomePage());
My problem is that the index is returning as -1
.
I believe there's some added complexity due to my page hierarchy/structure.
My Apps main page is a LoginPage
- and once the user has successfully logged in, they are pushed to a Modal page, which then has the rest of the navigation taking place from there.
await Navigation.PushModalAsync(new NavigationPage(new MainMenu()));
How can I find the current active page (that the user is viewing) - from a class that is not inheriting from a ContentPage, when the NavigationStack includes a modal page?
I need to do this so that my static 'helper' class (which the platform specific iOS / Android code accesses) can be passed a string for the page name, and this helper class can resolve that name, and navigate to the page if it exists. The helper class is being accessed via platform-specific implementations after an event on the phone (push notification tapped on)
Upvotes: 1
Views: 7752
Reputation: 453
(NEW) SOLUTION #2
Another way to do this, is to subclass the NavigationPage, TabbedPage classes, then use the navigation events Xamarin gives us to track the current page. These events are: ModalPushed, ModalPopped, Pushed, Popped, CurrentPageChanged, etc.
It's a little more code but it is more predictable behavior across all platforms and you don't need to save any additional state if the app is put into the background.
1) App.xaml.cs
public App()
{
...
this.ModalPushed += OnModalPushed;
this.ModalPopped += OnModalPopped;
}
//keep track of any modals presented
private readonly Stack<Page> ModalPages = new Stack<Page>();
void OnModalPushed(object sender, ModalPushedEventArgs e)
{
ModalPages.Push(e.Modal);
PageService.CurrentPage = FindCurrentPage();
}
void OnModalPopped(object sender, ModalPoppedEventArgs e)
{
ModalPages.Pop();
PageService.CurrentPage = FindCurrentPage();
}
public Page FindCurrentPage()
{
//If there is a Modal present, start there, or else start in the MainPage
Page root = ModalPages.Count > 0 ? ModalPages.Peek() : this.MainPage;
var tabbedPage = root as TabbedPage;
if (tabbedPage != null)
{
var currentTab = tabbedPage.CurrentPage;
var navPage = currentTab as NavigationPage;
if (navPage != null)
{
return navPage.CurrentPage;
}
else
{
return currentTab;
}
}
else
{
var navPage = root as NavigationPage;
if (navPage != null)
{
return navPage.CurrentPage;
}
return root;
}
}
2) CustomNavigationPage.cs
//All NavigationPage in your app should use this class!
public class CustomNavigationPage : NavigationPage
{
public BaseNavigationPage(Page page) : base(page)
{
this.Pushed += OnPushed;
this.Popped += OnPopped;
}
void OnPushed(object sender, NavigationEventArgs e)
{
PageService.CurrentPage = e.Page;
}
void OnPopped(object sender, NavigationEventArgs e)
{
PageService.CurrentPage = ((App)App.Current).FindCurrentPage();
}
}
3) CustomTabbedPage.cs --- only if your app uses tabs
public class CustomTabbedPage : TabbedPage
{
public CustomTabbedPage()
{
this.CurrentPageChanged += OnTabbedPageTabChanged;
}
void OnTabbedPageTabChanged(object sender, EventArgs e)
{
PageService.CurrentPage = ((App)App.Current).FindCurrentPage();
}
}
4) PageService.cs
public static class PageService
{
public Page CurrentPage
{
get;
set;
}
}
(ORIGINAL) SOLUTION #1
What worked for me, was to use a static class to keep track of the page the user is currently on. I have all of my ContentPages inherit a BasePage which sets the current page in OnAppearing(). In Android, you also have to handle a special situation where the app Pauses/Resumes, because Xamarin will call OnAppearing on the root page of the application (not necessarily the page in view).
This method allows me to do behaviors like Push/Pop pages from anywhere in my viewmodel layer, without coupling the ViewModel directly to the View.
PageService.cs:
public static class PageService
{
public Page CurrentPage
{
get;
set;
}
public Page SavedStatePage
{
get;
set;
}
}
BasePage.cs:
//Have all of your pages inherit this page
public abstract class BasePage : ContentPage
{
public BasePage () : base()
{
}
public override void OnAppearing()
{
protected override void OnAppearing()
{
base.OnAppearing();
//Mainly for Android, restore the current page to the last saved page when the app paused
if( PageService.SavedStatePage != null )
{
PageService.CurrentPage = PageService.SavedStatePage;
PageService.SavedStatePage = null;
}
else
{
//default behavior. Set the current page to the one currently appearing.
PageService.CurrentPage = this;
}
}
}
}
MainActivity.cs (In native Droid project):
protected override void OnPause()
{
base.OnPause();
//save the current page before app pauses, because Xamarin doesn't always call OnAppearing on the correct page when resume happens
PageService.SavedStatePage = PageService.CurrentPage;
}
Upvotes: 4
Reputation: 732
Instead of pushing it directly using PushModalSync, why not instantiate it first and store it in a static variable in the Application. That way you could always reference it from where you like. From there you can then determine which page is the current page or do whatever navigation traversal you need to do.
Application.MyStaticVariable = new NavigationPage(new MainMenu());
await Navigation.PushModalAsync(Application.MyStaticVariable);
Upvotes: 0