Implementing MasterDetail layout in Xamarin.Forms by MvvmCross

In my Xamarin & MvvmCross Handbook, I demonstrated fundamentals to develop a basic Xamarin application with MvvmCross Framework. There are more details to consider when we develop a real application, such as the layout, the styles, and the database, etc. For example, Hamburger Menu layout is a very common navigation pattern in modern mobile applications. We can use MasterDetail navigation mode to implement the Hamburger Menu. Next, I’m going to show you how to implement a MasterDetail layout in the Xamarin.Forms application. Before we start, I recommend you to read the official documentation about MasterDetailPage here: Xamarin.Forms Master-Detail Page.

My dev environment is shown below:

  • Windows 10 version 10.0.17134
  • Visual Studio 2017 version 15.9.4
  • Xamarin.Forms version 3.4.0.1008975
  • MvvmCross version 6.2.2

Let’s get started.

Creating the project by MvxScaffolding

If you are new for MvvmCross, it might be a little bit tricky to create a Xamarin application with MvvmCross. Fortunately, we have some project templates to simplify our work. You can find them on the official documentation here: MvvmCross getting-started. I recommend you to use this one: MvxScaffolding It is new and supports .net standard. You can search it by clicking Tools-Extensions and Updates in your VS 2017, like this:

After installing it, you can create a new Xamarin.Forms application in MvvmCross category:

Input MvxFormsMasterDetailDemo as the project name. The MvxScaffolding provides us with a very friendly interface to customize the application. For a better understanding, we choose Blank template, as shown below:

The default setting doesn’t contain UWP project. Select it if you need to support UWP platform, and select the Min SDK version as 1803. It is recommended at the moment since some new features are not supported in the old Windows 10 versions. Also, you need to input the Description as the UWP application name.

Click the NEXT button and You will see a summary window. Check all the information, then click DONE button. The MvxScaffolding will generate a basic blank Xamarin.Forms application with a good structure.

Creating the MasterDetailPage

The MasterDetailPage is the root page of the application. Actually, it is an instance of MasterDetailPage class. It should not be used as a child page to ensure a consistent user experience in different platforms.

Creating the ViewModel

Next, add a new class file called MasterDetailViewModel in the ViewModels folder in the MvxFormsMasterDetailDemo.Core project. Change it to inherit from MvxViewModel class. Usually, we also need to use the NavigationService to implement the navigation in the ViewModel. So inject the instance of IMvxNavigationService by using dependency injection:

using MvvmCross.Navigation;
using MvvmCross.ViewModels;

namespace MvxFormsMasterDetailDemo.Core.ViewModels
{
public class MasterDetailViewModel : MvxViewModel
{
readonly IMvxNavigationService _navigationService;

public MasterDetailViewModel(IMvxNavigationService navigationService)
{
_navigationService = navigationService;
}
}
}

Creating the XAML file

Xamarin.Forms provides us with some navigation modes including Hierarchical Navigation, Tabbedpage, MasterDetailPage and Modal Pages, etc. For our requirements, we want to have a hamburger menu on the main page. So we can use MasterDetailPage, which is a root page of the application and contains two areas: the left one is the MasterPage, the right one is the DetailPage. We can place the menu in the MasterPage. When clicking the menu item, the navigation service will show another page in DetailPage area.

In MvvmCross, there are some corresponding MvxFromsPagePresenter for different page types in Xamarin.Forms, which define how a view will be displayed. We use MvxPagePresentationAttribute to specify different page types. For more details, please view the documentation here: Xamarin.Forms View Presenter.

Open the App.cs file in the MvxFormsMasterDetailDemo.Core project. Notice that the framework will start HomeViewModel as the first page. Now let us create a MasterDetailPage and use it to replace the first page.

Right click the Pages folder in the MvxFormsMasterDetailDemo.UI project and select Add-New Item. Select Content Page from Xamarin.Forms category, like this:

Open the MasterDetailPage.xaml file. Notice that this page is a ContentPage. We need to change it to inherit from MvxMasterDetailPage. Replace the XAML code with the following code:

<?xml version="1.0" encoding="utf-8" ?>
<views:MvxMasterDetailPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MvxFormsMasterDetailDemo.UI.Pages.MasterDetailPage"
xmlns:views="clr-namespace:MvvmCross.Forms.Views;assembly=MvvmCross.Forms"
xmlns:viewModels="clr-namespace:MvxFormsMasterDetailDemo.Core.ViewModels;assembly=MvxFormsMasterDetailDemo.Core"
x:TypeArguments="viewModels:MasterDetailViewModel">
</views:MvxMasterDetailPage>

We use MvxMasterDetailPage to replace the default ContentPage type. To do this, we need to add this code:

xmlns:views="clr-namespace:MvvmCross.Forms.Views;assembly=MvvmCross.Forms"

To set the ViewModel of the MasterDetailPage, we need to specify the x:TypeArguments with the value viewModels:MasterDetailViewModel. Do not forget to import the viewModels namespace by adding xmlns:viewModels="clr-namespace:MvxFormsMasterDetailDemo.Core.ViewModels;assembly=MvxFormsMasterDetailDemo.Core".

Open the MasterDetailPage.xaml.cs file, replace the base class of it from ContentPage to MvxMasterDetailPage<MasterDetailViewModel>. Add the MvxMasterDetailPagePresentation attribute to the class, as the following code:

using MvvmCross.Forms.Presenters.Attributes;
using MvvmCross.Forms.Views;
using MvxFormsMasterDetailDemo.Core.ViewModels;
using Xamarin.Forms.Xaml;

namespace MvxFormsMasterDetailDemo.UI.Pages
{
[XamlCompilation(XamlCompilationOptions.Compile)]
[MvxMasterDetailPagePresentation(Position = MasterDetailPosition.Root, WrapInNavigationPage = false, Title = "MasterDetail Page")]
public partial class MasterDetailPage : MvxMasterDetailPage<MasterDetailViewModel>
{
public MasterDetailPage()
{
InitializeComponent();
}
}
}

Let’s take a look at the MvxMasterDetailPagePresentation attribute. There are some very important properties of MvxMasterDetailPagePresentation. Position is an enum value that is used to indicate the type of the page, which is Root here. Please set the other properties as shown, otherwise, you might get some odd results.

Creating the MasterPage

The MasterPage, which is used to show a hamburger menu, is a ContentPage that contains a ListView. We will use data-binding to initialize the menu items.

Creating the ViewModel

Create a MenuViewModel class in the ViewModels folder in the MvxFormsMasterDetailDemo.Core project. Replace the content with the following code:

using System.Collections.ObjectModel;
using MvvmCross.Navigation;
using MvvmCross.ViewModels;

namespace MvxFormsMasterDetailDemo.Core.ViewModels
{
public class MenuViewModel : MvxViewModel
{
readonly IMvxNavigationService _navigationService;

public MenuViewModel(IMvxNavigationService navigationService)
{
_navigationService = navigationService;
MenuItemList = new MvxObservableCollection<string>()
{
"Contacts",
"Todo"
};
}

#region MenuItemList;
private ObservableCollection<string> _menuItemList;
public ObservableCollection<string> MenuItemList
{
get => _menuItemList;
set => SetProperty(ref _menuItemList, value);
}
#endregion
}
}

It has a MenuItemList property to store some menu items. For the simplicity, there are only two strings: Contacts and Todo. We also need to inject the instance of IMvxNavigationService in the constructor.

Creating the XAML file

Next, add a new ContentPage called MenuPage.xaml into the Pages folder in the MvxFormsMasterDetailDemo.UI project. Open the MenuPage.xaml file and replace the content by the following code:

<?xml version="1.0" encoding="utf-8" ?>
<views:MvxContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:MvvmCross.Forms.Views;assembly=MvvmCross.Forms"
xmlns:viewModels="clr-namespace:MvxFormsMasterDetailDemo.Core.ViewModels;assembly=MvxFormsMasterDetailDemo.Core"
x:Class="MvxFormsMasterDetailDemo.UI.Pages.MenuPage"
x:TypeArguments="viewModels:MenuViewModel" >
<ContentPage.Content>
<StackLayout>
<ListView></ListView>
</StackLayout>
</ContentPage.Content>
</views:MvxContentPage>

Open the MenuPage.xaml.cs file and set the base class and the attributes, as shown below:

using MvvmCross.Forms.Presenters.Attributes;
using MvvmCross.Forms.Views;
using MvxFormsMasterDetailDemo.Core.ViewModels;
using Xamarin.Forms.Xaml;

namespace MvxFormsMasterDetailDemo.UI.Pages
{
[XamlCompilation(XamlCompilationOptions.Compile)]
[MvxMasterDetailPagePresentation(Position = MasterDetailPosition.Master, WrapInNavigationPage = false, Title = "HamburgerMenu Demo")]
public partial class MenuPage : MvxContentPage<MenuViewModel>
{
public MenuPage ()
{
InitializeComponent ();
}
}
}

The Position property of the MvxMasterDetailPagePresentation attribute should be set to Master, which means that this page will be shown as the MasterPage of the MasterDetailPage. And there is another pitfall for the MasterPage: it must have the Title property set, otherwise, your application will be stuck. So you must set the Title property of the MvxMasterDetailPagePresentation attribute.

Now we need to set the data-binding for the ListView. We already have a MenuItemList in its ViewModel, so what we should do now is setting the ItemsSource of the ListView, like this:

<ListView ItemsSource="{Binding MenuItemList}">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding}"></TextCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

Currently, we just use the TextCell to show the menu text. Before we implement the full functionality of the hamburger menu, let us create the DetailPages.

Creating the DetailPages

For the simplicity, we only add two pages as the detail page.

Creating the ViewModels

Add two new files named as ContactsViewModel and TodoViewModel in the ViewModels folder of MvxFormsMasterDetailDemo.Core project. Make them to inherit from MvxViewModel class respectively:

using MvvmCross.ViewModels;

namespace MvxFormsMasterDetailDemo.Core.ViewModels
{
public class ContactsViewModel : MvxViewModel
{
}
}
using MvvmCross.ViewModels;

namespace MvxFormsMasterDetailDemo.Core.ViewModels
{
public class TodoViewModel : MvxViewModel
{
}
}

Creating the XAML files

Add two ContentPage files into the Pages folder of MvxFormsMasterDetailDemo.UI project and name them as ContactsPage.xaml and TodoPage.xaml. To use MvvmCross feature, we need to change them to inherit from MvxContentPage. Open the ContactsPage.xaml file and replace the content by the following code:

<?xml version="1.0" encoding="utf-8" ?>
<views:MvxContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MvxFormsMasterDetailDemo.UI.Pages.ContactsPage"
xmlns:views="clr-namespace:MvvmCross.Forms.Views;assembly=MvvmCross.Forms"
xmlns:viewModels="clr-namespace:MvxFormsMasterDetailDemo.Core.ViewModels;assembly=MvxFormsMasterDetailDemo.Core"
x:TypeArguments="viewModels:ContactsViewModel">
<ContentPage.Content>
<StackLayout>
<Label Text="Welcome to ContactsPage!"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage.Content>
</views:MvxContentPage>

The Label is used to indicate the current page.

Open the ContactsPage.xaml.cs file and update the content like this:

using MvvmCross.Forms.Presenters.Attributes;
using MvvmCross.Forms.Views;
using MvxFormsMasterDetailDemo.Core.ViewModels;
using Xamarin.Forms.Xaml;

namespace MvxFormsMasterDetailDemo.UI.Pages
{
[XamlCompilation(XamlCompilationOptions.Compile)]
[MvxMasterDetailPagePresentation(Position = MasterDetailPosition.Detail, NoHistory = true, Title = "Contacts Page")]
public partial class ContactsPage : MvxContentPage<ContactsViewModel>
{
public ContactsPage ()
{
InitializeComponent ();
}
}
}

The value of the Position property is MasterDetailPosition.Detail, which means this page should be located to the Detail area of the MasterDetailPage. The NoHistory property should be true so that there is no strange behavior for the navigation for different platforms. The Title property is used to show the page name on the top of the page.

Do the same changes to TodoPage.xaml and TodoPage.xaml.cs correspondingly. Do not forget to update the Text of the Label control to show the page name.

Implementing the Menu functionalities

Now we have all the pages that we need to show: The root page called MasterDetailPage, the MasterPage called MenuPage, and two DetailPages called ContactsPage and TodoPage. Next, we need to make the menu work properly.

Displaying the MasterPage and the DetailPage

Open the MasterDetailViewModel.cs file and override the ViewAppearing method, like this:

public override async void ViewAppearing()
{
base.ViewAppearing();
await _navigationService.Navigate<MenuViewModel>();
await _navigationService.Navigate<ContactsViewModel>();
}

The ContactsPage is used as the DetailPage when the app starts. Because we have specified the MvxMasterDetailPagePresentation attributes for MenuPage and ContactsPage, so MvvmCross will find the correct position to show them.

Open the App.cs file in the MvxFormsMasterDetailDemo.Core project, and replace the first page by MasterDetailPage:

public class App : MvxApplication
{
public override void Initialize()
{
RegisterAppStart<MasterDetailViewModel>();
}
}

Now we can launch the app for three platforms:

Android:

The default view is good. Xamarin.Forms automatically adds a hamburger icon button on the top left of the page. When we click the button, the MenuPage shows, but there is no page header. We will adjust the UI later.

iOS:

The default view of iOS is different with Android. There is no hamburger icon on the page. Another issue about the header bar of the MenuPage is the same with Android. It seems like that we need to add a hamburger icon and show the head bar. We will do it later.

UWP:

What happened? The MasterPage shows automatically and there is no default hamburger button.

For details about the MasterDetailPage navigation behavior, please read it here: MasterDetailPage Overview. According to the documentation, the master page should have a navigation bar that contains a button. But now we got some different results. Anyway, we can fix it by ourselves.

To fix the layout of UWP, just set the MasterBehavior property of the MasterDetailPage. It is an enum value which determines how the detail page will show in the MasterDetailPage. If you keep it as Default, it will show the DetailPage respectively for different platforms. That is why we got different results.

Open the MasterDetailPage.xaml file in the MvxFormsMasterDetailDemo.UI project. Add the MasterBehavior property in the Page definition and set it as Popover, which means the DetailPage will cover or partially cover the MasterPage:

<?xml version="1.0" encoding="utf-8" ?>
<views:MvxMasterDetailPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MvxFormsMasterDetailDemo.UI.Pages.MasterDetailPage"
xmlns:views="clr-namespace:MvvmCross.Forms.Views;assembly=MvvmCross.Forms"
xmlns:viewModels="clr-namespace:MvxFormsMasterDetailDemo.Core.ViewModels;assembly=MvxFormsMasterDetailDemo.Core"
x:TypeArguments="viewModels:MasterDetailViewModel" MasterBehavior="Popover">

</views:MvxMasterDetailPage>

To see the effect, run the UWP project and it looks like this:

Now it has the default hamburger button on the left top of the page. Run the Android and the iOS projects to make sure everything is not broken by the slight change. You might notice that there are still some differences between these three platforms. For example, the UWP project has the header bar, but Android and iOS don’t. The Android and UWP have the default hamburger button, but iOS doesn’t. We will fix them later.

Setting the menu navigation

When clicking the menu item, the app should show the correct DetailPage. Now let us set the Command for the menu item. Open the MenuViwModel.cs file in the MvxFormsMasterDetailDemo.Core project, and add a command, as shown below:

#region ShowDetailPageAsyncCommand;
private IMvxAsyncCommand<string> _showDetailPageAsyncCommand;
public IMvxAsyncCommand<string> ShowDetailPageAsyncCommand
{
get
{
_showDetailPageAsyncCommand = _showDetailPageAsyncCommand ?? new MvxAsyncCommand<string>(ShowDetailPageAsync);
return _showDetailPageAsyncCommand;
}
}
private async Task ShowDetailPageAsync(string param)
{
// Implement your logic here.
}
#endregion

This is an instance of IMvxAsyncCommand<T>, which contains a parameter from the data-binding. The type of the parameter is string, because we know the object in the MenuItemList is string. If you have some other generic types for the MenuItemList , remember to change the generic type to your real item type.

Then we need to set the data-binding for the command. Open the MenuPage.xaml file in the MvxFormsMasterDetailDemo.UI project and check current ItemTemplate:

<ListView ItemsSource="{Binding MenuItemList}">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding}"></TextCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

Here we just use a simple TextCell to show the menu text. How to bind the command to the ListView?

Before we start the data-binding, I recommend you to read this article: The Xamarin.Forms Command Interface. In Xamarin.Forms, some controls support Command inherently, such as Button, MenuItem, TextCell and some classes that derive from them. And SearchBar supports SearchCommand property that actually is an ICommand type. The RefreshCommand property of ListView is also an instance of ICommand interface.

For those controls that do not support ICommand directly, Xamarin.Forms provides a TapGestureRecognizer to support Command binding. For more details, please read this article: Adding a tap gesture recognizer. Keep in mind that although GestureRecognizer supports more gestures, such as pinch, pan, and swipe, only TapGestureRecognizer supports ICommand. Another limit is that the view element must support GestureRecognizers.

Now let me show you how to use TapGestureRecognizer to bind the Command to the menu item. First, set the x:Name property of the MenuPage:

<views:MvxContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:MvvmCross.Forms.Views;assembly=MvvmCross.Forms"
xmlns:viewModels="clr-namespace:MvxFormsMasterDetailDemo.Core.ViewModels;assembly=MvxFormsMasterDetailDemo.Core"
x:Class="MvxFormsMasterDetailDemo.UI.Pages.MenuPage"
x:TypeArguments="viewModels:MenuViewModel"
x:Name="MainContent">

We need the name to reference the current ViewModel of the page.

Update the ItemTemplate like this:

<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Padding="10">
<StackLayout.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding BindingContext.DataContext.ShowDetailPageAsyncCommand, Source={x:Reference MainContent}}"
CommandParameter="{Binding}">
</TapGestureRecognizer>
</StackLayout.GestureRecognizers>
<Label Text="{Binding}" VerticalOptions="Center"></Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>

We made some changes to the ItemTemplate:

First, use ViewCell to replace the default TextCell. ViewCell provides us with more flexibility to customize the UI. So we can define the ItemTemplate as you like. For instance, maybe we will add a icon for every menu item.

In the ViewCell element, use a StackLayout control as the container, which supports GestureRecognizers, so we can add the TapGestureRecognizer to the StackLayout.

In the TapGestureRecognizer element, I define two important properties, one is Command, another is CommandParameter. As I said in my previous articles, you must be very clear about your current DataContext that you bind to your view. For our case, I must find the command named ShowDetailPageAsyncCommand in the MenuViewModel, so I use Source={x:Reference MainContent} to get the source object, which is the current Page named MainContent. Now we can get the ViewModel of the page by BindingContext.DataContext, then use BindingContext.DataContext.ShowDetailPageAsyncCommand as the binding path. I have been a little bit confused about BindingContext.DataContext because it is different from the syntax in UWP. Notice that the full syntax is:

Command="{Binding Path=BindingContext.DataContext.ShowDetailPageAsyncCommand, Source={x:Reference MainContent}}"

When you put the Path for the first parameter of the command-binding, it can be eliminated.

For the CommandParameter, it is easier. Just bind the current string to it. So it is CommandParameter="{Binding}". If you use an object that contains some properties, just use CommandParameter="{Binding YourProperty}".

Next, let us update the command. Open MenuViewModel.cs files in the ViewModels folder in the MvxFormsMasterDetailApp.Core project again, and complete the ShowDetailPageAsync method, as the following code:

private async Task ShowDetailPageAsync(string param)
{
// Implement your logic here.
switch (param)
{
case "Contacts":
await mvxNavigationService.Navigate<ContactsViewModel>();
break;
case "Todo":
await mvxNavigationService.Navigate<TodoViewModel>();
break;
default:
break;
}
}
#endregion

This method receives a parameter, which come from the command-binding. So we can determine which page should be displayed as the DetailPage.

Now launch the app and observe the navigation behavior. There is another problem. When clicking the menu item, although the DetailPage shows correctly, the MenuPage still covers the DetailPage. So we must control the navigation behavior of the MasterPage. To do it, we need to install Xamarin.Forms to the MvxFormsMasterDetailDemo.Core project. You can install it by searching Xamarin.Forms in the NuGet Package Manager. Please install the same version of Xamarin.Forms with the other project to avoid reference errors. For my demo solution, I use Xamarin.Forms.3.4.0.1008975.

Add some code into the ShowDetailPageAsync method after the switch segment(This code snippet comes from https://github.com/MvvmCross/MvvmCross/issues/2995):

if (Application.Current.MainPage is MasterDetailPage masterDetailPage)
{
masterDetailPage.IsPresented = false;
}
else if (Application.Current.MainPage is NavigationPage navigationPage
&& navigationPage.CurrentPage is MasterDetailPage nestedMasterDetail)
{
nestedMasterDetail.IsPresented = false;
}

IsPresented is used to control whether the master page is presented. To hide the MasterPage, set it to false. For more details, read it here: Creating and Displaying the Detail Page.

Launch the app for all three platforms to make sure the menu work properly.

The other approaches to set the data-binding

Using the inherent Command of TextCell

The data-binding mechanism of XAML world is flexible. Actually, we have more than one way to achieve our goal. For example, if you only use TextCell to show the menu item, there is a simple way to do the navigation. As I said in the previous section, TextCell supports ICommand inherently. So we can use the data-binding syntax like this:

<DataTemplate>
<TextCell Text="{Binding}" Command="{Binding BindingContext.DataContext.ShowDetailPageAsyncCommand, Source={x:Reference MainContent}}" CommandParameter="{Binding}"></TextCell>
</DataTemplate>

This way is easier. When the user taps the TextCell, it will trigger the Command. But the disadvantage is that you cannot customize the UI of the menu item. TextCell only supports the text. If you would like to add some images or define a complex item layout, you must use ViewCell.

Using Bahaviors

Also, you might think we can use ItemSelectedor ItemTapped events. Definitely, we can! But unfortunately, these events do not implement ICommand interface, so we cannot use the data-binding directly. To use the ICommand binding, we need to use a Behavior to convert an event to a command, which is described here: Reusable EventToCommandBehavior.

You might be not familiar with Behaviors. Behavior comes from Blend SDK, which is a very useful library in XAML world. The behaviors can be attached to some controls and listen to some events, then invoke some commands in the ViewModel. It is a good way to add the support for Command pattern for those controls that were not designed to interact with commands. So we can use MVVM pattern gracefully, instead of using event handlers in the code-behind files.

You can follow the instructions in the official documentation to create your EventToCommandBehavior, but we can leverage a third-party library to do it quickly: Behaviors.Xamarin.Forms.Netstandard. It is not the official project, but is easy to use. You can install it to the MvxFormsMasterDetailApp.UI project by searching Behaviors.Xamarin.Forms through the NuGet Package Manager:

We can use this library to enable the ListView control to trigger our command in the ViewModel when selecting the item. To do this, add a bindable property called SelectedMenuItem in the MenuViewModel.cs file, which is used to indicate the current selected item, like this:

#region SelectedMenuItem;
private string _selectedMenuItem;
public string SelectedMenuItem
{
get => _selectedMenuItem;
set => SetProperty(ref _selectedMenuItem, value);
}
#endregion

Replace the ShowDetailPageAsyncCommand region we created in the previous section by the code shown below:

#region ShowDetailPageAsyncCommand;
private IMvxAsyncCommand _showDetailPageAsyncCommand;
public IMvxAsyncCommand ShowDetailPageAsyncCommand
{
get
{
_showDetailPageAsyncCommand = _showDetailPageAsyncCommand ?? new MvxAsyncCommand(ShowDetailPageAsync);
return _showDetailPageAsyncCommand;
}
}
private async Task ShowDetailPageAsync()
{
// Implement your logic here.
switch (SelectedMenuItem)
{
case "Contacts":
await _navigationService.Navigate<ContactsViewModel>();
break;
case "Todo":
await _navigationService.Navigate<TodoViewModel>();
break;
default:
break;
}
if (Application.Current.MainPage is MasterDetailPage masterDetailPage)
{
masterDetailPage.IsPresented = false;
}
else if (Application.Current.MainPage is NavigationPage navigationPage
&& navigationPage.CurrentPage is MasterDetailPage nestedMasterDetail)
{
nestedMasterDetail.IsPresented = false;
}
}
#endregion

Did you find the difference? I removed the parameter from the command and use SelectedMenuItem property in the ShowDetailPageAsync method. Next, we need to set the data-binding for the SelectedItem of the ListView. Open MenuPage.xaml file in the Pages folder in the MvxFormsMasterDetailApp.UI project, remove current ListView control, and add a new ListView as shown below:

<ListView x:Name="MenuList" ItemsSource="{Binding MenuItemList}" 
SelectedItem="{Binding SelectedMenuItem, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding}"></TextCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

By the following code SelectedItem="{Binding SelectedMenuItem, Mode=TwoWay}", we can set a TwoWay data-binding between the SelectedItem of the ListView and the SelectedMenuItem property in the ViewModel.

Import the namespace for the Behavior in the views:MvxContentPage definition by adding the following code: xmlns:behaviors="clr-namespace:Behaviors;assembly=Behaviors". Now we can use the behaviors prefix to use the behaviors in the library. Update the XMAL of the ListView as shown below:

<ListView x:Name="MenuList" ItemsSource="{Binding MenuItemList}" 
SelectedItem="{Binding SelectedMenuItem, Mode=TwoWay}">
<ListView.Behaviors>
<behaviors:EventHandlerBehavior EventName="ItemSelected">
<behaviors:InvokeCommandAction
Command="{Binding BindingContext.DataContext.ShowDetailPageAsyncCommand,
Source={x:Reference MainContent}}"></behaviors:InvokeCommandAction>
</behaviors:EventHandlerBehavior>
</ListView.Behaviors>
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding}"></TextCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

I place a Behaviors section for the ListView control. There are one behavior called EventHandlerBehavior, which will be trigged by the ItemSelected event. In the behavior, there is a InvokeCommandAction, which will invoke the ShowDetailPageAsyncCommand in the ViewModel. Notice the data-binding syntax. We need to specify the Source and the Path for the binding. If you just use {Binding ShowDetailPageAsyncCommand}, it will not work. So be careful the current BindingContext for your control.

Run the apps for three platforms and you will see it works as expected. You can choose any approach to implement the menu function. I just want to show you how to do it in different ways. Maybe you will use them for other scenarios.

Fine-tuning the UI

There are some defects to the UI for different platforms. For example, the page header and the hamburger menu icon for iOS are not as good as we expected. Let us fix them.

Adding the hamburger icon for iOS

According to the official documentation of MasterDetailPage, I think iOS should also show a button like Android and UWP, but actually not. We can set an Icon property for the MasterPage.

Download the image file here. Paste it into the Resources folder of the MvxFormsMasterDetailDemo.iOS project. If there is no such folder, create one. The Build Action property for the image should be BundleResource.

Open the MenuPage.xaml file in the Pages folder of the MvxFormsMasterDetailApp.UI project. Add the following code into the views:MvxContentPage section: Icon="hamburger.png". Now launch the app for iOS:

That is good!

Adding the header bar for Android and iOS

UWP will add a default header bar for the MasterPage. For Android and iOS, we need to define it respectively.

To provide some specific values for different platforms, we can use the Device class, which contains a number of properties and methods that can help us to customize the layout and the functionality for specific-platforms. You can read details about it here: Xamarin.Forms Device Class.

For our requirement, we only need to add the header bar for Android and iOS. Open the MenuPage.xaml file in Pages folder of MvxFormsMasterDetailDemo.UI project. Add the following code before the ListView definition:

<StackLayout HeightRequest="40">
<StackLayout.IsVisible>
<OnPlatform x:TypeArguments="x:Boolean">
<On Platform="Android, iOS" Value="True" />
<On Platform="UWP" Value="False" />
</OnPlatform>
</StackLayout.IsVisible>
<Label Text="My HamburgerMenu Demo" Margin="10" VerticalOptions="Center" FontSize="Large"></Label>
</StackLayout>

Actually, the OnPlatform markup is doing something that seems like creating a switch statement in the code. It contains several On classes to receive the Platform properties, which indicate the current platform. There are some different values to identify different platforms: iOS, Android, UWP and macOS. So we can create a StackLayout, which contains a Label control to show the app name, for Android and iOS by setting its IsVisible property. But for UWP, it is invisible. That means adding the code will not make any changes for UWP.

Run the app for Android and iOS. It works fine for Android. But on iOS platform, the header bar slightly overlays the status bar of the phone, as shown below:

We can add some margin for the StackLayout. Add another OnPlatform markup for iOS, like this:

<StackLayout.Margin>
<OnPlatform x:TypeArguments="Thickness">
<On Platform="iOS" Value="0,20,0,0" />
</OnPlatform>
</StackLayout.Margin>

It only impacts the UI for iOS. Now take a look for all platforms:

iOS:

Android:

UWP:

Ok, everything is good, except the item height of UWP…

Adjust the height of the item for UWP

You might notice that if we use TextCell as the item template of the ListView, there are default margins and styles for the items in the ListView for Android and iOS. But for UWP platform, there is no default styles and proper height for items. Let us define the style for item template. At the same time, we should make sure it works for every platform.

Open the MenuPage.xaml file in Pages folder of the MvxFormsMasterDetailDemo.UI project. Update the ItemTemplate by the following code:

<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout HeightRequest="50">
<Label Text="{Binding}" Margin="20,0,0,0"
VerticalOptions="CenterAndExpand"></Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>

That is it. At last, the whole file looks like this:

<?xml version="1.0" encoding="utf-8" ?>
<views:MvxContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:MvvmCross.Forms.Views;assembly=MvvmCross.Forms"
xmlns:viewModels="clr-namespace:MvxFormsMasterDetailDemo.Core.ViewModels;assembly=MvxFormsMasterDetailDemo.Core"
x:Class="MvxFormsMasterDetailDemo.UI.Pages.MenuPage"
x:TypeArguments="viewModels:MenuViewModel"
x:Name="MainContent"
xmlns:behaviors="clr-namespace:Behaviors;assembly=Behaviors"
Icon="hamburger.png">
<ContentPage.Content>
<StackLayout>
<StackLayout HeightRequest="40">
<StackLayout.IsVisible>
<OnPlatform x:TypeArguments="x:Boolean">
<On Platform="Android, iOS" Value="True" />
<On Platform="UWP" Value="False" />
</OnPlatform>
</StackLayout.IsVisible>
<StackLayout.Margin>
<OnPlatform x:TypeArguments="Thickness">
<On Platform="iOS" Value="0,20,0,0" />
</OnPlatform>
</StackLayout.Margin>
<Label Text="HamburgerMenu Demo" Margin="10" VerticalOptions="Center" FontSize="Large"></Label>
</StackLayout>
<ListView x:Name="MenuList" ItemsSource="{Binding MenuItemList}"
SelectedItem="{Binding SelectedMenuItem, Mode=TwoWay}">
<ListView.Behaviors>
<behaviors:EventHandlerBehavior EventName="ItemSelected">
<behaviors:InvokeCommandAction
Command="{Binding BindingContext.DataContext.ShowDetailPageAsyncCommand,
Source={x:Reference MainContent}}"></behaviors:InvokeCommandAction>
</behaviors:EventHandlerBehavior>
</ListView.Behaviors>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout HeightRequest="50">
<Label Text="{Binding}" Margin="20,0,0,0"
VerticalOptions="CenterAndExpand"></Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
</views:MvxContentPage>

It is time to launch the app for all three platforms and observe the final results! Now the application shows correct Hamburger menu for three platforms and it has proper margin and styles.

Summary

In this article, I have shown you how to create a basic hamburger menu layout for iOS, Android and UWP by Xamarin.Forms and MvvmCross Framework. I am not a professional designer so you might need to fine-tune the styles for your own applications. I hope you can follow these steps to create a hamburger menu layout with a clean, graceful MVVM architecture. Also, I would like you could get data-binding fundamentals from my demo. Bear in mind that there might be more than one approach to achieve the same goal, and my implementation is not the best one. Actually, I think it would be better to add a icon for each item! if you find a better solution, please leave comments and discuss it below.

You can find the repo on my GitHub: MvxFormsMasterDetailDemo. Happy coding!