XF vs PWA: XF Setting Up the Dashboard Page

The Goal

In this post we are going to setup the Dashboard page. The purpose of this page is to list the users favorite routes and stops so that no matter where they are, they can proceed straight to that information. It will be a relatively simple page, really just two lists. Tapping an item on one of the lists will navigate to the appropriate page and display the favorite route or stop.

Laying out the Page

This will be a pretty simple layout. Just use a grid, four rows, an auto height row for each title and an equal expanding height row for each of the lists.

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

    <Label Grid.Row="0" Text="My Favorite Stops" />
    <ListView Grid.Row="1" ItemsSource="{Binding Stops}"></ListView>

    <Label Grid.Row="2" Text="My Favorite Routes" />
    <ListView Grid.Row="3" ItemsSource="{Binding Routes}"></ListView>
</Grid>

ViewModel

The view model is going to be just as simple. Two ObservableCollections to handle the lists and a pair of commands to handle the user tapping on a list item. If you remember from setting up the hamburger menu, we are going to derive our view model from the BaseViewModel class.

public class DashboardPageViewModel : BaseViewModel
{
    private INavigationService _navigationService = null;
    private IFavoriteService _favoriteService = null;


    public DashboardPageViewModel(INavigationService navigationService, IFavoriteService favoriteService)
    {
        _navigationService = navigationService;
        _favoriteService = favoriteService;
    }


    // binds to the routes list view
    private ObservableCollection<FavoriteRouteViewModel> _routes = new ObservableCollection<FavoriteRouteViewModel>();
    public ObservableCollection<FavoriteRouteViewModel> Routes
    {
        get => _routes;
        set => SetProperty<ObservableCollection<FavoriteRouteViewModel>>(ref _routes, value);
    }

    // binds to the stops list view
    private ObservableCollection<FavoriteStopViewModel> _stops = new ObservableCollection<FavoriteStopViewModel>();
    public ObservableCollection<FavoriteStopViewModel> Stops
    {
        get => _stops;
        set => SetProperty<ObservableCollection<FavoriteStopViewModel>>(ref _stops, value);
    }


    // user taps on one of the stops
    private DelegateCommand<FavoriteStopViewModel> _commandGoToStop = null;
    public DelegateCommand<FavoriteStopViewModel> CommandGoToStop
    {
        get
        {
            return _commandGoToStop ??
                (_commandGoToStop = new DelegateCommand<FavoriteStopViewModel>((stop) => { }));
        }
    }

    // user taps on one of the routes
    private DelegateCommand<FavoriteRouteViewModel> _commandGoToRoute = null;
    public DelegateCommand<FavoriteRouteViewModel> CommandGoToRoute
    {
        get
        {
            return _commandGoToRoute ??
                (_commandGoToRoute = new DelegateCommand<FavoriteRouteViewModel>((route) => { }));
        }
    }
}

So how do we get the view model into the page itself? If you remember from the first post, we have to register all of our pages that we navigate to with the container, and we do that in our PrismApplication RegisterTypes method. In this case, we will register the dashboard page and the view model as follows:

public partial class App : PrismApplication
{
    // other stuff here

    protected override void RegisterTypes(IContainerRegistry containerRegistry)
    {
        containerRegistry.Register<IFavoriteService, TestFavoriteService>();
        containerRegistry.RegisterForNavigation<Views.DashboardPage, ViewModels.DashboardPageViewModel>(Pages.Dashboard);
        /// other registrations
    }
}

In the above code, we are telling the navigation service that when we navigate to the DashboardPage, we should also inject the view model into the page’s DataContext member. Of course the navigation service will make use of the Container to resolve any dependencies that might be required for the view model, such as IFavoriteService and INavigationService. Notice that we also register the IFavoriteService implementation in the RegisterTypes function. We don’t have to do that for INavigationService as it is a core part of the Prism framework and already taken care of in the PrismApplication base class.

Initializing the View Model

Things are looking pretty good, but how do we initialize the data in our view model? This is another part where the navigation service in Prism really helps us. After it finishes constructing the page, injecting the view model and making it the active view, it will test to see if the view model that was injected in the data context implements the INavigationAware interface (check out INavigatedAware and INavigatingAware for more granular implementations). INavigationAware looks like this:

public interface INavigationAware
{
    void OnNavigatingTo(INavigationParameters parameters);
    void OnNavigatedFrom(INavigationParameters parameters);
    void OnNavigatedTo(INavigationParameters parameters);
}

We will have our viewmodel implement the INavigationAware interface and then load our data when the ‘OnNavigatedTo’ method is called.

public class DashboardPageViewModel : BaseViewModel, INavigationAware
{
    /// all the other stuff from above here



    public void OnNavigatedFrom(INavigationParameters parameters)
    {
        // nothing doing here
    }

    public async void OnNavigatedTo(INavigationParameters parameters)
    {
        var favStops = await _favoriteService.GetFavoriteStopsAsync();
        /// do something here with the collection
        var favRoutes = await _favoriteService.GetFavoriteRoutesAsync();
        /// do something here with the routes collection
    }

    public void OnNavigatingTo(INavigationParameters parameters)
    {
        // nothing happening here
    }
}

And that is most of the work for the dashboard page. Really the only thing left to do is to make sure that the event that is raised from tapping one of the listview items is routed to our DelegateCommand objects in the view model. For an example of that, check out my article here.

Storing the Favorites Data

By now, you might be thinking, how am I going to store the data for the favorites in a platform independant way and still be able to use it with my shared view model? I am glad you asked, because I think that will be the next post!

code in GitHub

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

XF Platform Specific Styling

Introduction

Sometimes you need to specify XAML that is specific to the platform. Xamarin Forms already has a special class to help with this called OnPlatform. But I find that it isn’t always granular enough to handle the different instances of Windows: Windows, Windows Phone, Windows Tablet and Xbox. We can define our own custom handler to help us with this.

It looks like the following in XAML.

<!--
here is the declaration in the content page attributes:
xmlns:h="clr-namespace:AppName.Helpers"
-->
<Label Text="Display Custom Platform Styling">
    <Label.Margin>
        <h:OnCustomPlatform
            x:TypeArguments="Thickness"
            Android="5,40"
            iOS="10,20"
            Windows="20"
            WinPhone="4,8,4,8"
            WinTablet="12,8,12,8"
            Xbox="40" />
    </Label.Margin>
</Label>

A couple of things about the above code: the h: refers to the namespace that contains our OnCustomPlatform class. The other thing to note is that I don’t know if the Device and TargetIdiom objects in Xamarin Forms work with the Xbox, so use with caution if you are going to try this with an Xbox app.

How the Class Works

using Xamarin.Forms;

public sealed class OnCustomPlatform<T>
{
    public T Android { get; set; } = default(T);
    public T iOS { get; set; } = default(T);
    public T WinPhone { get; set; } = default(T);
    public T Windows { get; set; } = default(T);
    public T WinTablet { get; set; } = default(T);
    public T Xbox { get; set; } = default(T);
    public T Other { get; set; } = default(T);

    public static implicit operator T(OnCustomPlatform<T> p)
    {
        switch (Device.RuntimePlatform)
        {
            case Device.Android:
                return p.Android;
            case Device.iOS:
                return p.iOS;
            case Device.Windows:
                if (Device.Idiom == TargetIdiom.Desktop)
                    return p.Windows;
                else if (Device.Idiom == TargetIdiom.Phone)
                    return p.WinPhone;
                else if (Device.Idiom == TargetIdiom.Tablet)
                    return p.WinTablet;
                else if (Device.Idiom == TargetIdiom.TV)
                    return p.Xbox;
                else
                    return default(T);
            default:
                return p.Other;
        }
    }
}

And that is all there is. It is actually surprisingly simple to do though I admit, after a while it gets pretty verbose the more complex you get with your page.

XF vs PWA: Getting Started With XF

Setting Up Your Project

In my Xamarin Forms vs PWA experiment, I thought I would get started with Xamarin Forms first. The first thing we are going to do is create the project. For those that don’t know, you can find the Xamarin Forms project templates under cross-platform in the category tree on the left.

ProjectSetupXF1

Next you will have to select the type of project. I prefer to use the blank app template and the platforms depend on what I am doing. I find that even if I don’t need a UWP app, I select it anyways as I find it easiest for debugging. I am also a fan of using the standard library for the common code instead of the shared project: you are only compiling the shared project once instead of including it to be compiled for each of the clients. But both work so use whatever you prefer.

ProjectSetupXF2

Alright, so after we let it restore all of the necessary packages, we can get started. If you have read any of my other posts, you know that I am a big fan of the work that the team with Prism Library do, and back it up with a Patreon pledge (if you love Prism, you should consider it as well). So the next step will be to setup Prism with my app.

Adding Prism

The easiest thing to do here is obviously to use NUGET to add Prism to your project. You can search for Prism.Unity and that will bring in the packages that use Unity.Container as the dependency injection container.

PrismUnityPackage

I should note here that I am going to use the pre-release version of the packages. As such, I am going to go to the GitHub repository and download the packages and use them in my workspace. Go to the 7.1.0-Pre tag to download the latest that I used. Why would I download and include the projects manually? If you haven’t done this before, I would recommend trying it sometime with a library of your choice. If you are relying on a package of some kind, I think it is a good idea to have at least a base understanding of how things are working within the code that you are relying on. And if something isn’t working as expected, it is pretty easy to step right into the library code itself and see what is going on. This is the first time that I have used the 7.x version of Prism so I want to see what the changes are.

If you do download the source and add it to your project manually, you will have to include the projects in your solution. Once they are in the solution, remember to go the core project and each of the platform projects and add them as project references.

Bootstrapping Your App

If you have done any Xamarin Forms projects in the past, you know that there is an Application object that is used to start up your app. This object is contained in the core project common to all the platforms. We are going to change our Application object to derive from the PrismApplication object. If you are poking around, you will probably find a PrismApplicationBase object in the Prism.Forms assembly and a PrismApplication object in the Prism.Unity.Forms assembly. We want to use the one that is derived for the container of our choice: Unity.Container.

Head on over to your core project and open up the App.xaml file. You are going to need to update the root tag to be PrismApplication and do that you will need to add the appropriate namespace to the XAML namespace declarations. Your XAML would then look like the following:

    

Don’t forget the code behind.  In addition to change the base class, Prism imposes a pair of methods that you must define: RegisterTypes and OnInitialized. First, let’s fix up the class declaration and the constructor. For now, ignore the IPlatformInitializer, we will talk more about that in another post.

public partial class App : PrismApplication
{
    public App(IPlatformInitializer initializer = null)
        : base(initializer)
    {
    }

    /// ...
}

The RegisterTypes method is used to register all the navigation pages that the navigation service uses and all of the services that you want to consume in your app. An example might be the service that calls an external web api. Although we haven’t gotten to that point yet, this sample project will call a web api that gets data from the local transit authority. We will see that a bit later.

For now, all we want to do is to register our pages for navigation, of which we only have one.

protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
    containerRegistry.RegisterForNavigation("main");
}

That was pretty easy. If we had a view model for our MainPage defined yet, we could also register it as part of the above call. And then whenever the navigation service created the page, it would also create the view model and inject it for us.

Next, we look at the OnInitialized function. This gets called after all of the initialization has happened and your app is ready to show the first page. For us it will be very simple, we just call the base class InitializeComponent method and then ask the navigation service to go the first page.

protected override void OnInitialized()
{
    InitializeComponent();
    NavigationService.NavigateAsync("main");
}

In the above, the NavigationService looks for the page type that you registered against the “main” key, creates it and pops it up on to the Xamarin Forms navigation stack. Just to make sure everyone grasps the coolness part of the navigation service, you perform your navigation using arbitrary strings, therefore your objects are not coupled together and it is dead easy to swap in new pages without affecting the rest of your code.

Excellent! This should get you going with a shell app that you can run on each of the platforms that you created for. Once caveat though: I sometimes find Visual Studio to be a bit finicky when you start changing the XAML base class in the app object. I usually find that some combination of cleaning the solution and restarting Visual Studio clears this up, so be aware of that. Since I specified a UWP project as well, I haven’t tried to open it up in Visual Studio for Mac to see if that is less fragile. Once Visual Studio wraps its mind around it though, you should be good to go.

Next thing we will look at is adding in our master/detail, hamburger menu and place holder pages for the different options on the menu.

Visual Studio 2017: Xamarin Projects Not Being Created

Well, I ran into a bit of a problem this morning. I started up Visual Studio 2017 and tried to create a new Xamarin Forms Project targeting Android, iOS and UWP. However, all that happened was a solution file being created, none of the actual projects were created.

Well, that’s not what I want! I did some digging and it seems like during one of my Visual Studio 2017 updates, the Android SDK was removed and not updated. I fired up the Visual Studio installer and installed the latest SDK’s.

Started up Visual Studio again and … success! A new Xamarin Forms project was successfully created. Too bad I didn’t think to try creating a new project targeting just iOS and UWP: perhaps that would have pointed to the Android SDK being the problem quicker.

Hope this helps someone else.