Xamarin Forms & .NET Standard: basic navigation

In the last post about Xamarin Forms we’ve started to see the basic concepts on how to begin the development of a cross-Platform application. In this post we’re going to explore some basic navigation concepts and some tips to organize our solution. Also, we’ll develop a simple CrossPlatformApp that welcomes us with a main screen and a button to navigate to a new view where we can generate a random integer.

Organize our new project

We’ve just created a new Xamarin Forms Project and now we’re ready to develop yet another mobile app. If you know how to create a brand new Xamarin Forms Project you can continue reading, otherwise I suggest to read the first post (getting started) of this serie.

When I start a new Project I like to organize things to be more confortable and organized so let’s start and do it.

The Views folder

We create a new folder in the shared Project and call it Views. In this folder we’ll put all the files to create the UI (the views).

We already have a view in our Project: the MainPage.xaml. We move the file in the Views folder.

Now we move the MainPage class in the Views namespace. Besides, everytime we’ll add a new file in the Views folder the class will be created in the Views namespace as standard behavior by Visual Studio.

To move the MainPage class into the Views namespace we edit MainPage.xaml and MainPage.xaml.cs like this:

Now our Project won’t compile because the App class cannot find the MainPage class anymore. So we need to edit the App class like the following image:

Now our soution compiles.

Setup the navigation

With navigation we mean the features available in Xamarin Forms to drive the user experience from a view to another. In this example we’re going to implement a hierarchical navigation.

To support hierarchical navigation we create a shell for our UI where the views will be navigated. The shell is created with an instance of the NavigationPage class. So we edit the App.xaml.cs file to set the MainPage of the App as a new NavigationPage. The root of our navigation is the MainPage that is a ContentPage.

If we build and run our application we’ll see no UI differences in the app but now we’re ready to navigate from one page to another.

The CrossPlatformApp for UWP.

Let’s navigate

As we stated at the beginning of this post the app has to show to the user a button to navigate to another view. So let’s edit the XAML in the MainPage class to do this.

<?xml version="1.0" encoding="utf-8" ? /&gt;

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:CrossPlatformApp" x:Class="CrossPlatformApp.Views.MainPage"<span style="display: inline !important; float: none; background-color: transparent; color: #3d596d; cursor: text; font-family: 'Noto Serif',Georgia,'Times New Roman',Times,serif; font-size: 16px; font-style: normal; font-variant: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-decoration: none; text-indent: 0px; text-transform: none; -webkit-text-stroke-width: 0px; white-space: normal; word-spacing: 0px;">/></span>

<StackLayout>
<Label Text="Welcome to cross platform random integer generator!" VerticalOptions="Center" HorizontalOptions="Center" <span style="display: inline !important; float: none; background-color: transparent; color: #3d596d; cursor: text; font-family: 'Noto Serif',Georgia,'Times New Roman',Times,serif; font-size: 16px; font-style: normal; font-variant: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-decoration: none; text-indent: 0px; text-transform: none; -webkit-text-stroke-width: 0px; white-space: normal; word-spacing: 0px;">/></span>
<Button Text="Start!" Clicked="OnStartClicked" x:Name="buttonStart" <span style="display: inline !important; float: none; background-color: transparent; color: #3d596d; cursor: text; font-family: 'Noto Serif',Georgia,'Times New Roman',Times,serif; font-size: 16px; font-style: normal; font-variant: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-decoration: none; text-indent: 0px; text-transform: none; -webkit-text-stroke-width: 0px; white-space: normal; word-spacing: 0px;">/></span>
</StackLayout<span style="display: inline !important; float: none; background-color: transparent; color: #3d596d; cursor: text; font-family: 'Noto Serif',Georgia,'Times New Roman',Times,serif; font-size: 16px; font-style: normal; font-variant: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-decoration: none; text-indent: 0px; text-transform: none; -webkit-text-stroke-width: 0px; white-space: normal; word-spacing: 0px;">/></span>
</ContentPage<span style="display: inline !important; float: none; background-color: transparent; color: #3d596d; cursor: text; font-family: 'Noto Serif',Georgia,'Times New Roman',Times,serif; font-size: 16px; font-style: normal; font-variant: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-decoration: none; text-indent: 0px; text-transform: none; -webkit-text-stroke-width: 0px; white-space: normal; word-spacing: 0px;">/></span>

In the respective code-behind file (MainPage.xaml.cs) we write:

public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
        }

        private async void OnStartClicked(object sender, EventArgs e)
        {
            await Navigation.PushAsync(new RandomIntegerView(new RandomIntegerViewModel(new RandomGenerator())));
        }
    }

In the code above we trigger the navigation in the handler of the click event of the button. We use the Navigation property to get a reference to the Navigation system and then we call PushAsync to put the view where we’ll generate a random number on the stack of the hierarchical navigation.

untitled

Before we move on we give a closer look this line of code:

await Navigation.PushAsync(new RandomIntegerView(new RandomIntegerViewModel(new RandomGenerator()))); 

Here we’re creating an instance of the RandomIntegerView passing in the constructor the ViewModel and in its constructor a reference to a service which can generate random numbers. Then we push this view onto the navigation stack.

We now have some errors from the compiler because we’ve written some names of not existing classes. We move on and create those classes.

In the Views folders we create a new RandomIntegerView class like this:

8

In the RandomIntegerView.xaml file we write:

<xml version="1.0" encoding="utf-8" ?&gt;
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"              xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"              x:Class="CrossPlatformApp.Views.RandomIntegerView"<span style="display: inline !important; float: none; background-color: transparent; color: #3d596d; cursor: text; font-family: 'Noto Serif',Georgia,'Times New Roman',Times,serif; font-size: 16px; font-style: normal; font-variant: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-decoration: none; text-indent: 0px; text-transform: none; -webkit-text-stroke-width: 0px; white-space: normal; word-spacing: 0px;">></span>
    <ContentPage.Content<span style="display: inline !important; float: none; background-color: transparent; color: #3d596d; cursor: text; font-family: 'Noto Serif',Georgia,'Times New Roman',Times,serif; font-size: 16px; font-style: normal; font-variant: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-decoration: none; text-indent: 0px; text-transform: none; -webkit-text-stroke-width: 0px; white-space: normal; word-spacing: 0px;">></span>
        <StackLayout<span style="display: inline !important; float: none; background-color: transparent; color: #3d596d; cursor: text; font-family: 'Noto Serif',Georgia,'Times New Roman',Times,serif; font-size: 16px; font-style: normal; font-variant: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-decoration: none; text-indent: 0px; text-transform: none; -webkit-text-stroke-width: 0px; white-space: normal; word-spacing: 0px;">></span>
            <Label Text="Generate your lucky number!" /<span style="display: inline !important; float: none; background-color: transparent; color: #3d596d; cursor: text; font-family: 'Noto Serif',Georgia,'Times New Roman',Times,serif; font-size: 16px; font-style: normal; font-variant: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-decoration: none; text-indent: 0px; text-transform: none; -webkit-text-stroke-width: 0px; white-space: normal; word-spacing: 0px;">></span>
            <Button x:Name="buttonGenerator" Text="Generate" Command="{Binding GenerateRandomInteger}" /<span style="display: inline !important; float: none; background-color: transparent; color: #3d596d; cursor: text; font-family: 'Noto Serif',Georgia,'Times New Roman',Times,serif; font-size: 16px; font-style: normal; font-variant: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-decoration: none; text-indent: 0px; text-transform: none; -webkit-text-stroke-width: 0px; white-space: normal; word-spacing: 0px;">></span>
            <Label Text="{Binding LuckyNumber}" FontSize="32" /<span style="display: inline !important; float: none; background-color: transparent; color: #3d596d; cursor: text; font-family: 'Noto Serif',Georgia,'Times New Roman',Times,serif; font-size: 16px; font-style: normal; font-variant: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-decoration: none; text-indent: 0px; text-transform: none; -webkit-text-stroke-width: 0px; white-space: normal; word-spacing: 0px;">></span>
        </StackLayout<span style="display: inline !important; float: none; background-color: transparent; color: #3d596d; cursor: text; font-family: 'Noto Serif',Georgia,'Times New Roman',Times,serif; font-size: 16px; font-style: normal; font-variant: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-decoration: none; text-indent: 0px; text-transform: none; -webkit-text-stroke-width: 0px; white-space: normal; word-spacing: 0px;">></span>
    </ContentPage.Content<span style="display: inline !important; float: none; background-color: transparent; color: #3d596d; cursor: text; font-family: 'Noto Serif',Georgia,'Times New Roman',Times,serif; font-size: 16px; font-style: normal; font-variant: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-decoration: none; text-indent: 0px; text-transform: none; -webkit-text-stroke-width: 0px; white-space: normal; word-spacing: 0px;">></span>
</ContentPage>

We now have our view to generate random numbers composed by a generic label that states what to view does (generate a number), a button to trigger the number calculation, and a label which displays the lucky number.

The code-behind of the view is:

using CrossPlatformApp.ViewModels;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace CrossPlatformApp.Views
{
    public partial class RandomIntegerView : ContentPage
    {
        public RandomIntegerView(RandomIntegerViewModel viewModel)
        {
            BindingContext = viewModel;

            InitializeComponent();
        }
    }
}
9
UWP flavor of the random integer generator

As we can see from the XAML code we use the MVVM pattern with binding to link the action of the “Generate” button to a Command that will handle the number generation in the ViewModel. The visualization of the result is performed with a label with binding in the Text property to a LuckyNumber member of the ViewModel.

The RandomIntegerViewModel and the .Core project

Now we need the RandomIntegerViewModel to give life to our UI. We create a ViewModels folder in the shared Project and create a new class into it named RandomIntegerViewModel.

10

We also create a new .NET Standard assemply Project called CrossPlatformApp.Core where we’ll put all the services and interfaces definition that our app will need.

11

Inside this new Project we create an interface that states what a lucky number generator has to do: we call it IIntegerGenerator and the code is:

namespace CrossPlatformApp.Core
{
public interface IIntegerGenerator
{
int GenerateInt();
}
}

Now we create a RandomGenerator class that implements IIntegerGenerator like this:

using System;

namespace CrossPlatformApp.Core
{
    public class RandomGenerator : IIntegerGenerator
    {
        public int GenerateInt()
        {
            Random r = new Random(DateTime.Now.Second);

            return r.Next();
        }
    }
}

The final result is:

12.png

Now we can go back to our RandomIntegerViewModel and write:


using CrossPlatformApp.Core;
using System.ComponentModel;
using System.Windows.Input;
using Xamarin.Forms;

namespace CrossPlatformApp.ViewModels
{
    public class RandomIntegerViewModel : INotifyPropertyChanged
    {
        private ICommand _generateRandomInteger;
        private IIntegerGenerator _integerGeneratorService;
        private int _luckyNumber;

        public RandomIntegerViewModel(IIntegerGenerator integerGeneratorService)
        {
            _integerGeneratorService = integerGeneratorService;
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public ICommand GenerateRandomInteger
        {
            get
            {
                if (_generateRandomInteger == null)
                {
                    _generateRandomInteger = new Command(() =&gt;
                   {
                       LuckyNumber = _integerGeneratorService.GenerateInt();
                   });
                }

                return _generateRandomInteger;
            }
        }

        public int LuckyNumber
        {
            get
            {
                return _luckyNumber;
            }
            private set
            {
                if (_luckyNumber != value)
                {
                    _luckyNumber = value;

                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(LuckyNumber)));
                }
            }
        }
    }
}

What we’ve done is to define the Command (the GenerateRandomInteger Command) to generate the lucky number and the property to allow the display of the result in the view (the LuckyNumber property). As we can see we also injected into the constructor the dependency from the service that creates the random integer. This way our class is more testable. We implemented the INotifyPropertyChanged to enable the comunication from the ViewModel to the View.

Now we can hit F5 and try the app.

13.png
The Android version of the app when running

We can navigate back to the main page if we press the back button of the OS:

14
Home sweet home (Android version)

TL; DR

In this post we created a simple app to handle the basics of the navigation mechanism and implement a hierarchical navigation.
We also organized our solution with a Core Project to host all the services and interfaces that are useful to all the three versions of our app. A core Project is useful because promotes code reuse and testability. We also explored the concept of services where we can implement non-UI features and inject them in our view-models.

Source code: https://github.com/phenixita/CrossPlatformRandomGenerator

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.