Login Flow in Xamarin.Forms Shell

October 30th 2020 Xamarin

If you create a new Xamarin.Forms Shell project based on the Flyout template, it's already going to include some boilerplate for the login process. To make it work for my use case, I had to make some improvements to it.

Remove login page from the flyout menu

Although not immediately obvious, the login page is by default still listed in the flyout menu just below the Logout menu item. You can't notice it because it doesn't have a label. But you can still click on it.

To fix that, you first need to upgrade the Xamarin.Forms NuGet package in your solution to version 4.8 or higher. This will add the IsVisible property to the FlyoutItem allowing you to completely remove the login page from the flyout menu. To do that, you need to wrap the existing ShellContent element for LoginPage with a FlyoutItem element:

<FlyoutItem IsVisible="False">
    <ShellContent Route="LoginPage"
                  Shell.FlyoutBehavior="Disabled"
                  ContentTemplate="{DataTemplate local:LoginPage}"/>
</FlyoutItem>

If you're like me, you might find the original comment above this line somewhat misleading. If you're wondering why the flyout menu is disabled when this page is displayed, it's because of the Shell.FlyoutBehavior="Disabled" attribute.

Update: In the Flyout template in Visual Studio 2019 16.8, this issue is already fixed. It uses a different approach which unlike my fix above still works in Xamarin.Forms 5, so I suggest you always use the following markup instead:

<TabBar>
    <ShellContent Route="LoginPage"
                  Shell.FlyoutBehavior="Disabled"
                  ContentTemplate="{DataTemplate local:LoginPage}"/>
</TabBar>

Force users to log in on application start

If your application requires the users to log in, you will need to check at the application startup whether the user has a valid login. Depending on the result, you will show him either the login page or the application home page:

Check user login on startup

I implemented the check in a simple page showing only an ActivityIndicator:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ShellLogin.Views.StartupPage"
             Shell.NavBarIsVisible="False">
    <ContentPage.Content>
        <StackLayout VerticalOptions="Center">
            <ActivityIndicator IsRunning="True" />
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

The Shell.NavBarIsVisible="False" attribute hides the navigation bar at the top. The rest should be self-explanatory.

I added the page as the first (hidden) FlyoutItem in the Shell and disabled the flyout menu for it:

<FlyoutItem IsVisible="False">
    <ShellContent Route="StartupPage"
                  Shell.FlyoutBehavior="Disabled"
                  ContentTemplate="{DataTemplate local:StartupPage}" />
</FlyoutItem>

I implemented the check in the OnAppearing virtual method of the page:

protected override void OnAppearing()
{
    base.OnAppearing();

    CheckLogin();
}

private async Task CheckLogin()
{
    // should check for valid login instead
    await Task.Delay(2000);

    // only open Login page when no valid login
    await Shell.Current.GoToAsync($"//{nameof(LoginPage)}");
}

In a real application, I would most likely make an API call and redirect to the correct page accordingly instead of always opening the login page. It's important to use the // prefix in the Uri to create a new navigation stack for the page.

Open additional pages on the login navigation stack

You might need more than one page for the user's login flow. For example, you could also provide the account registration functionality:

Add account registration functionality

As this page won't be a part of the Shell hierarchy, its route must be explicitly registered in the code (typically in the AppShell class constructor):

Routing.RegisterRoute(nameof(RegisterPage), typeof(RegisterPage));

To navigate to it, use only the route name:

await Shell.Current.GoToAsync($"{nameof(RegisterPage)}");

Since the page is not included in the Shell hierarchy, the flyout menu can't be disabled there. Instead, the Shell.FlyoutBehavior="Disabled" attribute must be set in the page itself:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ShellLogin.Views.RegisterPage"
             Shell.FlyoutBehavior="Disabled">
    <ContentPage.Content>
      <!--- ... -->
    </ContentPage.Content>
</ContentPage>

When navigating away from this page, it's important to reset the navigation stack to the root page so that it will be ready when navigating back to it after a potential logout:

await Shell.Current.Navigation.PopToRootAsync(animated: false);
await Shell.Current.GoToAsync($"//{nameof(AboutPage)}");

A sample application with the complete login flow implemented is available in my GitHub repository.

The login page in the flyout template for Xamarin.Forms Shell project is a good starting point which can be further extended without too much work. The fact that the login page is accessible from the flyout menu is its only real problem. To fix it, the Xamarin.Forms NuGet package must be updated. With some knowledge about the Shell behavior additional pages can be added to the application:

  • to be shown during the application initialization, or
  • to be included in the login flow navigation stack.

Get notified when a new blog post is published (usually every Friday):

Copyright
Creative Commons License