Navigation Best Practice?

Poodle989Poodle989 AUMember ✭✭
edited February 19 in Xamarin.Forms

Hey all,

I am currently working on a cross platform app and need some advice as to the best practice when handling navigation pages.

Currently I have a login/sign in page where if a successful login occurs then I will store the session token to handle persistent authentication so the user won't need to enter the username and password every time they kill and reopen the app:

public App()
    {
        InitializeComponent();

        Init();
    }

private async void Init()
    {
        bool validToken = await SecureStorage.GetAsync(Constants.AUTH_TOKEN_ID) != null;
        if (validToken)
        {
            MainPage = new NavigationPage(NavigationManager.MainPage);
        }
        else
        {
            MainPage = new NavigationPage(NavigationManager.SignInPage);
        }
    }

When Login Button is clicked and successful authenticates credentials:

private async void LoginButton_Clicked(object sender, EventArgs e)
    {
        try
        {
            await Navigation.PushAsync(NavigationManager.MainPage);
        }
        catch(Exception ex)
        {
            await DisplayAlert("Error.", ex.Message, "Ok");
            return;
        }
    }

If the user decides to logout manually then I am clearing the tokens from secure storage and setting the main page to be SignInPage again:

private async void LogoutButton_Clicked(object sender, EventArgs e)
    {
        App.Current.MainPage = new NavigationPage(NavigationManager.SignInPage);
    }

My problem is that if I now try to enter user credentials and sign back I get the error message "Page must not already have a parent"

Note: NavigationManager is a singleton that is used to store instances for each page instead of constantly creating new instances.

public static class NavigationManager
{
    private static SignInPage signInPage;
    public static SignInPage SignInPage
    {
        get
        {
            if (signInPage == null)
            {
                signInPage = new SignInPage();
            }

            return signInPage;
        }
    }

    private static MainPage mainPage;
    public static MainPage MainPage
    {
        get
        {
            if(mainPage == null)
            {
                mainPage = new MainPage();
            }

            return mainPage;
        }
    }
}

Anyone have any suggestions on how to fix this error? Or a better way of handling navigation.

Answers

  • LandLuLandLu Member, Xamarin Team Xamurai

    Since you used the singleton to create the mainPage and signInPage, once you have used Navigation.PushAsync(NavigationManager.MainPage) to show your main page this page has been wrapped by NavigationPage. After logging out and logging in, you want to navigate to the main page which has been in a navigation stack again. This is the issue's root cause.
    I recommend you to use a normal instance instead of a singleton:

    public class NavigationManager
    {
        private SignInPage signInPage;
        public SignInPage SignInPage
        {
            get
            {
                if (signInPage == null)
                {
                    signInPage = new SignInPage();
                }
    
                return signInPage;
            }
        }
    
        private MainPage mainPage;
        public MainPage MainPage
        {
            get
            {
                if (mainPage == null)
                {
                    mainPage = new MainPage();
                }
    
                return mainPage;
            }
        }
    }
    

    Consume it like:

    var navigationManager = new NavigationManager();
    bool validToken = await SecureStorage.GetAsync(Constants.AUTH_TOKEN_ID) != null;
    if (validToken)
    {
        MainPage = new NavigationPage(navigationManager.MainPage);
    }
    else
    {
        MainPage = new NavigationPage(navigationManager.SignInPage);
    }
    

    Another approach is changing the App's MainPage directly instead of pushing:

    private void LoginButton_Clicked(object sender, EventArgs e)
    {
        App.Current.MainPage = new NavigationPage(NavigationManager.MainPage);
    }
    
Sign In or Register to comment.