Strange NavigationBar behaviour in Xamarin Forms - xamarin

I have a Login page that is the starting point of my application. This page has navigation bar hidden with the following code:
NavigationPage.SetHasNavigationBar(this, false);
From Login page I start my Dashboard page using the following code:
Navigation.PushAsync(new Dashboard());
I do this on two seperate places inside my Login page. First place is inside constructor of Login page where I check for active user session and the second place is inside method that handles button click for login.
So I call Navigation.PushAsync() like this:
1st way:
public Login()
{
NavigationPage.SetHasNavigationBar(this, false);
InitializeComponent();
var loginSession = App.DataService.CheckUserSession();
if(loginSession != null)
{
Navigation.PushAsync(new Dashboard());
}
}
2nd way:
public void OnLogin(object o, EventArgs e)
{
Navigation.PushAsync(new Dashboard());
}
And this is where things start to go strange. If I open my Dasboard page with button click (using 2nd example) the Dasboard page gets loaded normally and has it's own NavigationBar. However everytime that I open Dasboard page when user session is found (using 1st way), my Dasboard page will have missing NavigationBar. To make things even more strange, if I navigate from Dasboard page to another page (lets say Settings) and then return back to Dasboard, navigation bar will then work normally on Dasboard page. I have tried removing the code that hides navigation bar on Login page NavigationPage.SetHasNavigationBar(this, false);
and what this does is that Login page will have it's navigation bar shown, but upon navigation to Dashboard (using 1st way), navigation bar will remain the same as on Login page.
I was maybe wondering if the problem lies within the fact that all my pages (Login, Dashboard and Settings) are child's of ContentPage and would have to instead inherit from NavigationPage?
I am really confused about what's happening with my NavigationBar at this moment.

It's because the Dashboard page is being pushed from inside the Login constructor. Instead, override OnAppearing() and do it there:
protected override async void OnAppearing()
{
base.OnAppearing();
await Navigation.PushAsync(new Dashboard());
}
Also, your children pages should inherit from ContentPage as nesting NavigationPages could lead to other issues :)

Related

Why doesn't Navigation.PopAsync() trigger the OnAppearing method of the underlying page?

My Xamarin Forms Application has a MainPage set to
new NavigationPage(new CarsMasterDetailPage())
where CarsMasterDetailPage : MasterDetailPage.
In CarsMasterDetailPage constructor, I set the Master property to new CarsMasterPage() and the Detail property to new CarsDetailPage(). Both CarsMasterPage and CarsDetailPage extend ContentPage.
The master page contains a list of cars and a button which has an event handler that does:
await Navigation.PushAsync(new AddCar());
The AddCar page has a button with an event handler that does:
await Navigation.PopAsync();
When I first run the app, the OnAppearing method of the master page is called. The first time the navigation pops back to the master page, OnAppearing is called again. Subsequent navigation pushes and pops don't, though.
I can get around it by adding a delegate on the master page that the add car page calls when it's done, but that feels hacky since there are page events to handle this. Does anyone know why it's not working?
In my case, I did it like this and everything works fine:
My MainPage:
MainPage = new MasterDetailPage {
Master = new MasterDetailPage1Master(),
Detail = new MyNavigationPage(new MainPageLinearList(appType))
};
So the key point was to use NavigationPage in a DetailPage. After that everything works fine.
Subscribe to the NavigationPage.Popped event:
navigationPage.Popped += OnPopped;
...
void OnPopped(object sender, NavigationEventArgs e)
{
var currentPage = (sender as NavigationPage).CurrentPage;
var removedPage = e.Page;
...
}

CarouselView not showing on Android if in a modal page

I am trying to handle a login/registration form inside a CarouselView (https://github.com/alexrainman/CarouselView). In my MainPage, I have this:
protected override async void OnAppearing()
{
base.OnAppearing();
// Is user logged in?
if (!(bool)Application.Current.Properties["IsLoggedIn"]) // He is not logged in
{
await this.Navigation.PushModalAsync(new LoginRegistrationPage());
}
}
LoginRegistrationPage contains a CarouselView and its first page is the registration form, while the second page is the login form.
iOS is fine, but on Android I get a blank page. The CarouselView is visible everywhere except that in a modal page. (I have tried to set LoginRegistrationPage as MainPage and to put it inside a simple Navigation rather than a modal one, both work).
It was a bug of CarouselView.
This solved: https://github.com/alexrainman/CarouselView/pull/329

Menu button not visible on MasterDetailPage after removing previous page

The main page of the app is set to a NavigationPage.
I have a common scenario where I have a login page (ContentPage) and then I navigate to app's main page (MasterDetailPage).
The code I run on login page is something like this:
var mainPage = new MasterDetailTestPage();
await this.Navigation.PushAsync(mainPage);
this.Navigation.RemovePage(this); // remove login page
The issue is the menu button is not visible on the MasterDetailPage.
If instead of this I set the main page to MasterDetailPage on app start like this:
MainPage = new NavigationPage (new MasterDetailTestPage());
in this case the menu button is shown. But this only works if it's set at the beginning. It doesn't work if app starts with login page and then setting MainPage.
It looks like the MasterDetailTestPage doesn't display menu button unless it's the root page of NavigationPage.
I reproduced this on a sample app too.
MasterDetailPage with button will only appear if it sets in root page a NavigationPage. In your case you shoud do something like this.
public partial class App : Application
{
public App()
{
InitializeComponent();
MainPage = new NavigationPage(new MenuPage());
}
The MenuPage Contains the MasterDetailPage with a List, and then when navigates to another page(LoginPage) you can do your logic and then :
public async void login_clicked(object sender ,EventArgs e)
{
//.....your logic
await Navigation.PopToRootAsync();
}
So now you can return and navigate to your MasterDetailPage with a visible button, because it removes all pages that was in stack. I hope it helps you.

Xamarin.Forms removing a page

When my app first starts up I have it display a login page. In the login button if they are able to login I want to then remove the login page and navigate to a tabbed page. In this tabbed page I'll have a settings page that would allow me to get back to the login page if needed. Right now I have the following but it doesn't work. The HomePage is shown but the back arrow to the login page shows up and I don't want that.
public class LoginPage: ContentPage
{
public LoginPage() { // create controls here }
public btnLogin_Clicked(object sender, EventArgs e){
Navigation.PopAsync(); // remove this page (doesn't work)
Navigation.PushAsync(new HomePage());
}
}
public class App : Application
{
public App()
{
MainPage = new NavigationPage(new LoginPage());
}
}
Xamarin.Forms 1.3 added the capability to add and remove pages resetting the root of the navigation stack as you suggest. Your code indicates that you are using at least version 1.3. However, calling PopAsync() right off the bat is not the method you want to use as it will not pop off a page if it is the only page in the stack. Instead use the INavigation interface's InsertPageBefore(newPage, pageToPutBefore) method first and then pop the login page off the end of the stack.
You can try code similar to this:
public async void btnLogin_Clicked(object sender, EventArgs e)
{
// Do some login logic and if successful ...
Navigation.InsertPageBefore(new HomePage(), this);
await Navigation.PopAsync().ConfigureAwait(false);
}
There are several new methods in Xamarin.Forms 1.3 that substantially improve the navigation capabilities. Another possible solution to the above problem would be to first add the HomePage to the end of the stack and then use the new RemovePage method to remove the login page from the start of the stack leaving the HomePage as the only page left. One thing you want to be careful of, if you are adding the new page using an asynchronous method like PushAsync you will need to await to call to ensure the new page is finished being added to the stack before removing the old page.
Yet another solution: Change MainPage property
in App.cs Constructor:
public App()
{
MainPage = new LoginPage();
}
in your Login method:
Device.BeginInvokeOnMainThread(() =>
{
Application.Current.MainPage = new NavigationPage(new HomePage());
});
For your second point , the back arrow to the login page shows up and you don't want that >>
Use this NavigationPage.SetHasBackButton(YourPage, false);
This will remove the Back Button from your navigation bar
As an example for your code above,
HomePage myHomePage = new HomePage();
NavigationPage.SetHasNavigationBar(myHomePage , false);
Navigation.PushAsync(myHomePage);
You can explore more methods of NavigationPage - such as SetHasNavigationBar and many more, they are really good.
Please let me know if this helps.

Showing different toolbar buttons on each page with Xamarin Forms

I have 2 pages in my Xamarin Forms app. My first page has 4 icons in the toolbar. My second page is a login page and has a tick and a cross in the toolbar.
I can't get the login page to show any icons unless I make it a navigation page. I also have to clear ToolBarItems on the first page before calling PushAsync() otherwise it complains there are too many toolbar items.
If I call PopAsync() on the login page it does not return to the first page. I'm guessing this is due to their being 2 navigation pages. I also tried PopToRootAsync().The back button works however.
My question is - how do I show different toolbar icons on 2 different pages in a way that allows navigation to work?
I'm testing this on Windows Phone 8.0
Here is the code calling the login page:
private async void ShowLoginPage()
{
ToolbarItems.Clear();
var page = new NavigationPage(new LoginPage());
await Navigation.PushAsync(page);
}
and here is the code to return to the first page:
private void Cancel()
{
Navigation.PopToRootAsync();
}
I'm running Xamarin.Forms v1.2.2.6243
One thing you could try is to keep your Login Page inside of a NavigationPage, and then instead of running PopAsync() within the Login Page after they have logged in successfully, simply replace the MainPage with your old Navigation page:
In your App class:
public NavigationPage AppNavPage = new NavigationPage(new FirstPage());
public App() {
MainPage = AppNavPage;
}
In your FirstPage:
private async void ShowLoginPage() {
ToolbarItems.Clear();
var page = new NavigationPage(new LoginPage());
await Navigation.PushAsync(page);
}
In Login Page:
private async void OnCreateClicked(object sender, EventArgs e) {
bool loginInfoIsGood = CheckLoginInfo(); //Check their login info
if(loginInfoIsGood) {
Application.Current.MainPage = App.AppNavPage;
}
}
Otherwise, I have also done a custom renderer for the NavigationRenderer on iOS to insert toolbar items onto the right side of the Navigation Bar and have overridden some Menu related stuff on Android to change the icon text/colors.
One option that you have, and one that I implemented in my own app, is a custom renderer that removes the navigation header from the app and then you could build your own custom header. With this approach, you do lose some of the native feel of the app, and you have to implement much of the transitional functionality your self. However, it gives you alot more control over the look.
CustomRenderer that removes the navigationBar:
//add using statements
// add all view here that need this custom header, might be able to build a
//base page that others inherit from, so that this will work on all pages.
[assembly: ExportRenderer(typeof(yourView), typeof(HeaderRenderer))]
class HeaderRenderer : PageRenderer
{
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
this.NavigationController.SetNavigationBarHidden(true, true);
}
}
After this you can build a header view that can be placed on the top of every page (I am using xaml) so I don't know if it is relevant in you application.
Edit: You might need to change this renderer for differnt page types.

Resources