Xamarin.Forms Pages: MasterDetailPage

mobile, xamarin comments

Hello buddies! This post closes the Pages section here in the blog, in this last post I will show the MasterDetailPage, samples and, as I always do, a practical application of this kind of page. I hope you like it!

MasterDetailPage

As its own name says, the MasterDetailPage is used to show a pair of pages where one of these pages is the Master page that will work as a navigation and the other page is the Detail page that will detail the info selected in the master page.

MasterDetailPage on Smartphones

On smartphones, by its reduced size, we can’t show the Master page and the Detail page at the same time, in this case, by default, the Detail page is shown and when we drag the page laterally from the left corner to the right corner, or when we click in the navigation bar button, the Master page is slided on top of the Detail page, hiding the Detail page partially and becoming completely visible.

The most recent MasterDetailPage sample for those who follow Xamarin news is the Xamarin Evolve’s app, in this app the Master page acts as a menu where we got the following items: Sessions, Speakers, Favorites, Places, About and Sponsors; when a menu item is selected, the Master page is hidden and the Detail page is loaded with the section content.

Aplicativo Xamarin Evolve

MasterDetailPage on Tablets

On tablets, as we have a bigger screen, is possible to show both pages side-by-side and a sample of its use is the Gmail app, where, in the Master page we got a list of emails and then, when one of them is selected, it’s shown in details in the Detail page.

Aplicativo Gmail

Places App

In this post we will use the MasterDetailPage to create an app called Places, the Places is an app that lists places accordingly to three possible categories: Food, Shopping and Sports.

To build Places, we will use a MasterDetailPage, where the Master page will have a categories menu, and when one of these categories is selected we will show name, address and photo of places that belong to the selected category.

Technically, our solution will be the following:

A MasterDetailPage, where the Master page will be a ContentPage with a ListView that will list the categories and a Detail page that will show one of the three ContentPages where each one represents a category. Each one of these categories pages will also use a ListView to show the list of places.

Creating the Master page: Menu

As we discussed above, the Menu page will be a ContentPage with a ListView that will list categories and each category is related to another ContentPage, so let’s first create the Category class.

The Category class is composed by a Name and a function that will return its ContentPage.

public class Category
{
	public string Name {
		get;
		set;
	}

	public Func<ContentPage> PageFn {
		get;
		set;
	}

	public Category (string name, Func<ContentPage> pageFn)
	{
		Name = name;
		PageFn = pageFn;
	}
}

Now, we will create a ListView to list the categories using the TextCell template (one of the built-in Xamarin Templates). Furthermore, we will set page’s Title (required in a Master page) and the Icon that will be displayed in the navigation bar.

We can see what I’m saying in the code below:

public class MenuPage : ContentPage
{
	public MenuPage ()
	{
		Title = "Menu";
		Icon = "menu.png";

		Padding = new Thickness (10, 20);

		var categories = new List<Category> () {
			new Category("Food", () => new FoodCategoryPage()),
			new Category("Shopping", () => new ShoppingCategoryPage()),
			new Category("Sports", () => new SportsCategoryPage())
		};

		var dataTemplate = new DataTemplate (typeof(TextCell));
		dataTemplate.SetBinding (TextCell.TextProperty, "Name");

		var listView = new ListView () {
			ItemsSource = categories,
			ItemTemplate = dataTemplate
		};

		Content = listView;
	}
}

Creating the MasterDetailPage

The next step is to create the MasterDetailPage, the MasterDetailPage has the properties Master and the Detail of type Page that represent the pages that we are already discussing in this post.

In the code below, I’m creating the MasterDetail class that inherits from MasterDetailPage. As for now, we only got the MenuPage, I will initialize the Master property with an instance of MenuPage and the Detail property with a blank ContentPage.

public class MasterDetail : MasterDetailPage
{
	public MasterDetail ()
	{
		Master = new MenuPage ();

		// Por enquanto vazia
		Detail = new NavigationPage(new ContentPage ());
	}
}

When running the code above on smartphones we got the following result:

MasterDetailPage with a Master Page

Creating the Categories

Food page

Our lists of places should be equal to the screens above, for that, we will use a ListView that will list objects of type ‘Place’ and then, we will use a template to guarantee that our objects will be rendered exactly in the way we want.

The Place class has the following properties: Name, Icon and Address.

public class Place
{
	public string Name {
		get;
		set;
	}

	public string Icon {
		get;
		set;
	}

	public string Address {
		get;
		set;
	}

	public Place (string name, string icon, string address)
	{
		Name = name;
		Icon = icon;
		Address = address;
	}
}

In this listing we will use a template called ImageCell, another built-in template from the Xamarin.Forms API. The ImageCell is a template composed by three parts: an icon, a text and a detail. Did you perceive that it’s exactly what we need?

In the template config, we will associate the Name property from the Place class to the Text property in the template, the Icon to the ImageSource and the Address to the Detail accordingly to the code below.

var imageTemplate = new DataTemplate (typeof(ImageCell));
imageTemplate.SetBinding (ImageCell.TextProperty, "Name");
imageTemplate.SetBinding (ImageCell.ImageSourceProperty, "Icon");
imageTemplate.SetBinding (ImageCell.DetailProperty, "Address");

The rest of the code is simple and is repeated for each category.

public class FoodCategoryPage : ContentPage
{
	public FoodCategoryPage ()
	{
		Title = "Food";

		Padding = new Thickness (10, 20);

		var places = new List<Place> () 
		{
			new Place("McDonalds", "mcdonalds.png", "1010 S McKenzie St Foley, AL"),
			new Place("Burger King", "burgerKing.png", "910 S McKenzie St Foley AL"),
			new Place("Apple Bees", "appleBees.png", "2409 S McKenzie St, Foley, AL"),
			new Place("Taco Bell", "tacoBell.jpg", "1165 S McKenzie St, Foley, AL"),
			new Place("Subway", "subway.jpg", "610 S McKenzie St Foley, AL")
		};

		var imageTemplate = new DataTemplate (typeof(ImageCell));
		imageTemplate.SetBinding (ImageCell.TextProperty, "Name");
		imageTemplate.SetBinding (ImageCell.ImageSourceProperty, "Icon");
		imageTemplate.SetBinding (ImageCell.DetailProperty, "Address");

		var listView = new ListView () 
		{
			ItemsSource = places,
			ItemTemplate = imageTemplate
		};

		Content = listView;
	}
}

public class ShoppingCategoryPage : ContentPage
{
	public ShoppingCategoryPage ()
	{
		Title = "Shopping";

		Padding = new Thickness (10, 20);

		var places = new List<Place> () 
		{
			new Place("Walmart", "walmart.jpeg", "2200 S McKenzie St, Foley"),
			new Place("Tanger Outlet Center", "tanger.jpg", "2601 S McKenzie St, Ste 466, Foley, AL"),
			new Place("Radio Shack", "radioShack.jpg", "1190 S Mckensie, Foley, AL")
		};

		var imageTemplate = new DataTemplate (typeof(ImageCell));
		imageTemplate.SetBinding (ImageCell.TextProperty, "Name");
		imageTemplate.SetBinding (ImageCell.ImageSourceProperty, "Icon");
		imageTemplate.SetBinding (ImageCell.DetailProperty, "Address");

		var listView = new ListView () 
		{
			ItemsSource = places,
			ItemTemplate = imageTemplate
		};

		Content = listView;
	}
}

public class SportsCategoryPage : ContentPage
{
	public SportsCategoryPage ()
	{
		Title = "Sports";

		Padding = new Thickness (10, 20);

		var places = new List<Place> () 
		{
			new Place("The Gulf Bowl", "gulfBowl.jpg", "2881 S Juniper St, Foley, AL"),
			new Place("Foley Sports Complex", "foleySportsComplex.jpg", "998 W Section Ave, Foley, AL"),
			new Place("Swatters Sports Complex", "swattersSportsComplex.jpg", "21431 Co Rd 12 S Foley, AL")
		};

		var imageTemplate = new DataTemplate (typeof(ImageCell));
		imageTemplate.SetBinding (ImageCell.TextProperty, "Name");
		imageTemplate.SetBinding (ImageCell.ImageSourceProperty, "Icon");
		imageTemplate.SetBinding (ImageCell.DetailProperty, "Address");

		var listView = new ListView () 
		{
			ItemsSource = places,
			ItemTemplate = imageTemplate
		};

		Content = listView;
	}
}

When executing the code above on an iPhone we got the following screens:

iPhone Categories

Linking the Categories to the Detail Page

To link the categories to the Detail page we need to do some code changes in our app.

The first is to pick one of the pages to be the default page (the app can’t start with a blank page, right?), lets set the FoodCategoryPage as our default page.

public class MasterDetail : MasterDetailPage
{
    public MasterDetail ()
    {
	    Master = new MenuPage ();
	    Detail = new NavigationPage(new FoodCategoryPage ());
    }
}

The second change is to change the MenuPage's code to when we select a category in the ListView, the Detail page be changed to show the places that belong to the selected category. For that, we will use the ItemSelected event that when triggered will call the OnMenuSelect action that will be configured by the MenuPage’s caller.

The MenuPage’s code is the following:

public class MenuPage : ContentPage
{
	public Action<ContentPage> OnMenuSelect {
		get;
		set;
	}

	public MenuPage ()
	{
		Title = "Menu";
		Icon = "menu.png";

		Padding = new Thickness (10, 20);

		var categories = new List<Category> () {
			new Category("Food", () => new FoodCategoryPage()),
			new Category("Shopping", () => new ShoppingCategoryPage()),
			new Category("Sports", () => new SportsCategoryPage())
		};

		var dataTemplate = new DataTemplate (typeof(TextCell));
		dataTemplate.SetBinding (TextCell.TextProperty, "Name");

		var listView = new ListView () {
			ItemsSource = categories,
			ItemTemplate = dataTemplate
		};

		listView.ItemSelected += (object sender, SelectedItemChangedEventArgs e) => {
			if (OnMenuSelect != null)
			{
				var category = (Category) e.SelectedItem;
				var categoryPage = category.PageFn();
				OnMenuSelect(categoryPage);
			}				
		};


		Content = listView;
	}
}

And for last, the MasterDetail page needs to configure the MenuPage’s OnMenuSelect action to, when a new category is selected by the user, we set a new page to the Detail page and hide the Master page.

menuPage.OnMenuSelect = (categoryPage) => {
	Detail = new NavigationPage(categoryPage);
	IsPresented = false;
};

Below is the MasterDetail’s code:

public class MasterDetail : MasterDetailPage
{
	public MasterDetail ()
	{
		var menuPage = new MenuPage ();
		menuPage.OnMenuSelect = (categoryPage) => {
			Detail = new NavigationPage(categoryPage);
			IsPresented = false;
		};

		Master = menuPage;

		Detail = new NavigationPage(new FoodCategoryPage ());
	}
}

Tablets: Bigger screens, more possibilities

On tablets we have bigger screens and it opens more possibilities when using a MasterDetailPage, using the MasterBehavior enumeration we can change the Master page’s behavior.

namespace Xamarin.Forms
{
	public enum MasterBehavior
	{
		Default,
		SplitOnLandscape,
		Split,
		Popover,
		SplitOnPortrait
	}
}
MasterBehavior = MasterBehavior.Default;

Using MasterBehavior as MasterBehavior.Default, in portrait mode the Master page will start hidden and will be always visible in the landscape.

MasterBehavior.Default

Using MasterBehavior as MasterBehavior.Popover, the Master page will start hidden in both modes (Portrait and Landscape).

MasterBehavior.Popover

Using MasterBehavior as MasterBehavior.Split, the Master page will be always visible in both modes (Portraid and Landscape).

MasterBehavior.Split

Using MasterBehavior as MasterBehavior.SplitOnLandscape, the Master page will be always visible in the landscape mode and will start hidden in the portrait.

MasterBehavior.SplitOnLandscape

And, for last, using MasterBehavior as MasterBehavior.SplitOnPortrait, the Master page will be always visible in the portrait mode and will start hidden in the landscape.

MasterBehavior.SplitOnPortrait

This is it!

I really liked this post! It was very complete about the MasterDetailPage’s use and, as an extra, we also have a functional menu that we can use in our apps. Together with the post about ContentPage and NavigationPage and the post about TabbedPage and CarouselPage this post closes the Pages section here in the blog. Starting next post we will talk about the kinds of Layouts!

See you around!

The code used in this post is available in my github.

Comments