XF vs PWA: XF Setup the Hamburger Menu

Introduction

Ok, now that we have a functioning app, the next thing we need to do is setup the hamburger menu that will allow the user to navigate to the various parts of the application.
In this app, we have a dashboard page that will list the favorite routes and stops for the user; a stops page that will show all the stops near the current location of the user; a routes page that will show the current location of the buses on the route; a donation page that will allow the user to make in-app purchases to support the app; a settings page that will allow the user to configure the app; an about page that displays app information and credits.

Create the Master Detail Page

In this, blog post, I am not going to get much into the styling of the apps and just concentrate on the functionality. I am going to post the results periodically and you can see any styling that I do there.
The master detail page has two sections: the master section and the detail section. The master section will contain the menu and the detail section will contain the menu selection. I don’t like all of the stuff that gets added in when you create a new MasterDetailPage in the “New Item” templates. I usually just add a new ContentPage and change the base class in the XAML and code behind. I also just declare the Master section contents in the MasterDetailPage itself.

The menu itself with be our app navigation. It will contain items for the dashboard, the route page, the stops page, donate, settings and about. Tapping on one of those items will navigate the detail section to that page.

<?xml version="1.0" encoding="utf-8" ?>
<MasterDetailPage
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    x:Class="Mvb.Views.MvbMasterPage"
    Title="METROVAN BUS BUDDY">

    <MasterDetailPage.Resources>
        <ResourceDictionary>
            <converters:MenuItemTypeConverter x:Key="menuImageConverter" />

            <Style x:Key="MenuItemLabelStyle" TargetType="{x:Type Label}" 
                    BasedOn="{StaticResource MontserratRegularLabelStyle}">
                <Setter Property="TextColor" Value="{StaticResource WhiteColor}" />
                <Setter Property="FontSize" Value="{StaticResource MediumSize}" />
            </Style>

        </ResourceDictionary>
    </MasterDetailPage.Resources>

    <MasterDetailPage.Master>
        <ContentPage Title="MENU">
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="*" />
                </Grid.RowDefinitions>

                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*" />
                        <RowDefinition Height="2*" />
                        <RowDefinition Height="*" />
                    </Grid.RowDefinitions>

                    <ListView Grid.Row="1"
                              x:Name="menuItems"
                              ItemsSource="{Binding MenuItems}"
                              b:ItemTappedCommandListView.ItemTappedCommand="{Binding CommandNavigate}"
                              SeparatorVisibility="None"
                              BackgroundColor="Transparent"
                              VerticalOptions="StartAndExpand">

                        <ListView.ItemTemplate>
                            <DataTemplate>
                                <ViewCell>
                                    <StackLayout Orientation="Horizontal">
                                        <StackLayout.Triggers>
                                            <DataTrigger TargetType="StackLayout" Binding="{Binding IsEnabled,Mode=TwoWay}" Value="False">
                                                <Setter Property="Opacity" Value="0.6" />
                                            </DataTrigger>
                                        </StackLayout.Triggers>

                                        <Image Source="{Binding MenuItem,Converter={StaticResource menuImageConverter}}"
                                               Aspect="AspectFit"
                                               WidthRequest="22"
                                               HeightRequest="22"
                                               Margin="10,0"
                                               VerticalOptions="Center" />

                                        <Label Text="{Binding Title}"
                                               Style="{StaticResource MenuItemLabelStyle}"
                                               HorizontalOptions="Center"
                                               VerticalOptions="Center" />
                                    </StackLayout>
                                </ViewCell>
                            </DataTemplate>
                        </ListView.ItemTemplate>
                    </ListView>

                </Grid>
            </Grid>

        </ContentPage>
    </MasterDetailPage.Master>
</MasterDetailPage>

The main thing that I want to call out in the above XAML is that the menu is implemented as a ListView and we are implementing each choice with a DataTemplate that will display as an icon and a label. The icon is determined by an IValueConverter that converts the MenuType enum to an image source (see my post on IValueConverters). I should also mention that we add a behavior to the ListView so that when an item is tapped, it executes an ICommand. I think it best if I document that in a separate post.

The MasterPage View Model

The viewmodel for the master page is actually pretty sparse. It has an ObservableCollection of MenuItemViewModels and the ICommand used to navigate when the user taps one of the menu items. Just to complete the picture, below is the class for the menu item view model and the master page view model.

    public enum MenuItemType
    {
        Dashboard,
        Stops,
        Route,
        Donate,
        Settings,
        About,
    }

    // BaseItemViewModel implements INotifyPropertyChanged and invokes it through
    // the SetProperty<> function. 
    public class MenuItemViewModel : BaseItemViewModel
    {
        public MenuItemViewModel()
        {
        }

        public MenuItemViewModel(string title, MenuItemType menuItem, string navigationPath)
        {
            _title = title;
            _menuItem = menuItem;
            _navigationPath = navigationPath;
        }


        #region properties


        private string _title = null;
        public string Title
        {
            get => _title;
            set => SetProperty<string>(ref _title, value);
        }


        private MenuItemType _menuItem;
        public MenuItemType MenuItem
        {
            get => _menuItem;
            set => SetProperty<MenuItemType>(ref _menuItem, value);
        }


        private string _navigationPath = null;
        public string NavigationPath
        {
            get => _navigationPath;
            set => SetProperty<string>(ref _navigationPath, value);
        }


        #endregion
    }


Here is the important part of the master page view model:

    public class MvbMasterPageViewModel : BaseViewModel
    {
        private ILoggerFacade _logService = null;
        private INavigationService _navigationService = null;

        public MvbMasterPageViewModel(IEventAggregator messageBus, ILoggerFacade logService, INavigationService navigationService)
            : base(messageBus)
        {
            _navigationService = navigationService;
            _logService = logService;
            InitalizeMenuItems();
        }



        private void InitalizeMenuItems()
        {
            /// create menu items. The important thing is that we are defining a label for displaying
            /// in the menu, a menu type that will convert into a menu icon and a navigation path.
            /// repeat for each menu item. See below for a bit more info on Prism navigation.
            _menuItems.Add(new MenuItemViewModel("Dashboard", MenuItemType.Dashboard, $"{Pages.MyNavPage}/{Pages.Dashboard}"));
            // ...
        }


        private ObservableCollection<MenuItemViewModel> _menuItems = new ObservableCollection<MenuItemViewModel>();
        public ObservableCollection<MenuItemViewModel> MenuItems
        {
            get => _menuItems;
            set => SetProperty<ObservableCollection<MenuItemViewModel>>(ref _menuItems, value);
        }



        private DelegateCommand<MenuItemViewModel> _commandNavigate = null;
        public DelegateCommand<MenuItemViewModel> CommandNavigate
        {
            get
            {
                return _commandNavigate ??
                    (_commandNavigate = new DelegateCommand<MenuItemViewModel>(
                        async (menu) =>
                        {
                            if (menu != null && menu.IsEnabled)
                            {
                                try
                                {
                                    _logService.Log($"Navigating to {menu.NavigationPath}", Category.Info, Priority.None);
                                    await _navigationService.NavigateAsync(menu.NavigationPath);
                                }
                                catch (Exception e)
                                {
                                    Debug.WriteLine(e.ToString());
                                    _logService.Log(e.ToString(), Category.Exception, Priority.High);
                                    throw;
                                }
                            }
                        }));
            }
        }
    }

I just want to call out that we are using the dependency injection capabilities of Prism to automatically add in the required constructor parameters. Remember from the first post in this series, we set that up in our Application class.

The detail section of the master detail page is going to contain a custom navigation page. There isn’t much in there that is special, I just set the navigation bar background and text color.

Prism Navigation Service

Let’s revisit the Prism navigation system. If you remember from the previous post, the first thing we need to do is register our pages with the container in the RegisterTypes method in the Application base. Each page registration is associated with a string key value. When you want to perform a navigation, you provide a navigation path based on those key values. The underlying Xamarin Forms navigation system relies on the type of the page for navigation. The awesome thing for us is that Prism abstracts that glue away to simple string values so that you can move your navigation calls into your view model with the rest of the business logic. And you can mock the navigation service for unit testing.

In our case, our navigation stack will be the master detail page, the navigation page and then the actual content page. We end up passing the navigation service a string value such as “/MasterPage/NavPage/DashboardPage”. Prism parses the path into segments and looks up each in the container. It sees that the MasterPage is a MasterDetailPage and knows that the next segment should be put in the Detail section of the page. It then sees that this page is a navigation page, so it knows to put the final page, the DashboardPage, into the content of the navigation page. The navigation service also handles the other possible scenarios such as tabs and modal displays.

Back To MvbMasterPageViewModel

With the help of the NavigationService, the MvbMasterPageViewModel is performing most of the navigation in the app, so we need to do our navigation relative to the master page. Each of our menu items will have a navigation path of “NavPage/DestinationPage”. As there is no leading “/” character, the navigation service knows that it is a relative path and will figure it out relative to itself.

Sample Code

Credits

Obviously there is Prism Github Repo
Also, some of the helper classes in the MyXamarinUtils project. bike360 sample