Mobile Corner

Build a Xamarin.Forms Application with MvvmCross

Every mobile and desktop platform has its own technology stack, or stacks, that developers can use to build applications. It used to be acceptable to build an application for a single platform, or perhaps two, which meant learning and developing with the tools was relatively straightforward.

However, as the number of target platforms grows, so does the cost of building applications that work on each of the platforms. This is where cross-platform frameworks offer the ability to build an application once and have it target multiple platforms. In this article I'm going to look at Xamarin.Forms, a framework that sits on top of the cross-platform tooling offered by Xamarin, and how it can be used in conjunction with MvvmCross to rapidly develop applications for iOS, Android and Universal Windows Platform (UWP). Xamarin.Forms also supports MacOS, Tizen and Windows Presentation Foundation (WPF), giving it one of the widest ranges of platforms of any cross-platform framework.

The best way to demonstrate how to get started with Xamarin.Forms and MvvmCross is to jump in and build out an application. In this case our application is going to load a collection of contacts into a list, and then allow the user to select a contact to view their details. We'll get started by creating a new application based on the Mobile App (Xamarin.Forms) project template, shown in Figure 1.

Mobile App (Xamarin.Forms) New Project Template
[Click on image for larger view.] Figure 1. Mobile App (Xamarin.Forms) New Project Template

After giving the application a name, the next prompt, Figure 2, will be to define which application template to use, which platforms to support and which code sharing strategy to use. In this case we'll go with the Blank App, since we're going to create the pages and navigation ourselves; we'll select all three platforms and we'll use the .NET Standard code sharing strategy.

Before talking about the choice of code-sharing strategy, let's quickly look at what Xamarin.Forms is. Xamarin, now part of Microsoft, identified that a large proportion of applications being built would benefit from the layout being defined once in a platform-agnostic way, with it being rendered on each supported platform. While it is possible to define the layout of your application in code, most Xamarin.Forms applications define the layout of each page, and controls, of an application using XAML. Unlike XAML used for WPF or UWP applications, Xamarin.Forms XAML is platform agnostic, making it perfect for declarative layout in a separate library. The Code Sharing Strategy defines where the XAML files, and their associated code-behind files, will be placed, either in a Shared Project or in a .NET Standard library.

Customizing the New Cross-Platform App Template
[Click on image for larger view.] Figure 2. Customizing the New Cross-Platform App Template

Just as a side note, the Shared Project should never be used as it was originally created to allow for bulk sharing of files between projects such as between a Windows 8 application and a Windows Phone 8 application. This is no longer required. Instead, if you want to include platform-specific functionality into a class library, you should be looking at using the new multi-targeting support provided by Visual Studio.

The newly created solution is made up of four projects: three target, or head, projects that represent the three platforms that were selected when creating the project, and a .NET Standard library that contains the XAML for the application, App.xaml, and the first page of the application, MainPage.xaml. To make sure everything was created properly you should build and run each of the target platforms to make sure they run without issue.

Like other XAML platforms, Xamarin.Forms supports databinding and the MVVM pattern for wiring up data into the UI. Rather than writing logic directly into the code-behind files (for example, MainPage.xaml.cs is the code-behind file for MainPage.xaml), MVVM prescribes the use of a ViewModel that is connected to the page (that is, the View) using data binding.

Rather than storing our ViewModels in the same project as the Views, we're going to create another project based on the Class Library (.NET Standard) project template, shown in Figure 3. While pages and controls for Xamarin.Forms are defined in a platform-agnostic way, there is still the dependency on the Xamarin.Forms framework. By creating a separate project in which to place our ViewModels, we can create them in a way that doesn't lock them into Xamarin.Forms. This is less about reusing the ViewModels across other frameworks, but it helps to enforce a separation of concerns and makes it easier to test the ViewModels.

Creating a Class Library (.NET Standard)
[Click on image for larger view.] Figure 3. Creating a Class Library (.NET Standard)

After creating the new library, we need to add a reference to the library into each of the four other projects, as illustrated in Figure 4.

Add Reference to Core Project
[Click on image for larger view.] Figure 4. Add Reference to Core Project

Rather than manually creating instances of our ViewModel and wiring them up to the corresponding View, this is where we are going to leverage MvvmCross, an application framework (rather than a platform framework) that helps abstract the administrative code of structuring an application, while prompting a clear separation of concerns between different parts of your application.

Before we add a reference to MvvmCross, we'll make sure that all of the existing NuGet package references have been updated to the latest stable version. After doing this we'll add a reference to MvvmCross.Forms, shown in Figure 5 (don't use the StarterPack as that was deprecated with v6 of MvvmCross). Add the reference to MvvmCross.Forms to all projects except the class library that we created for the ViewModels.

Add NuGet Reference to MvvmCross.Forms
[Click on image for larger view.] Figure 5. Add NuGet Reference to MvvmCross.Forms

Next, go ahead and add a reference to the MvvmCross package to all projects.

Before we can start building out our application there are a few more administrative changes we need to make in order to take advantage of MvvmCross. We'll start in the Core library by renaming Class1 to App and setting it to inherit from the MvxApplication class, as shown in the following code:

public class App: MvxApplication
{
  public override void Initialize()
  {
    base.Initialize();

    RegisterAppStart<MainViewModel>();
  }
}

At this stage the role of the App class is simply to define what the entry point is for the application. One of the biggest differences between a vanilla Xamarin.Forms app and one that uses MvvmCross is that with MvvmCross the navigation is controlled at a ViewModel level. This includes the starting point, which in this case we're setting to be the MainViewModel. By convention, MvvmCross will associated this with a View called MainPage.

Currently MainViewModel doesn't exist, so we'll create a new folder called ViewModels and create a new class, MainViewModel, with the following code:

public class MainViewModel: MvxViewModel
{
  public string WelcomeText => "Hello Xamarin.Forms with MvvmCross";
}

That's it for the Core library. Next we'll look at the library that contains the XAML. Currently there are two XAML files, App.xaml and MainPage.xaml. App.xaml is used for holding resources such as colors, brushes, styles, templates etc that will be used across multiple pages of the application. As MvvmCross handles the majority of the application wide startup logic, you can remove everything out of the code behind file, App.xaml.cs, leaving on the constructor, with a call to InitializeComponent (required in order to parse the XAML containing resources and so on):

public partial class App : Application
{
  public App ()
  {
    InitializeComponent();
  }
}

There are a few more changes required for the MainPage.xaml, but we'll start by moving it into a newly created folder called Views. Next, we need to change the base class of the MainPage from ContentPage to MvxContentPage. While we're updating the XAML, we'll also change the Label so that it is data bound to the WelcomeText property on our MainViewModel. Don't forget to update the base class in both the XAML and the code-behind file, MainPage.xaml.cs:

<?xml version="1.0" encoding="utf-8" ?>
<views:MvxContentPage
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:views="clr-namespace:MvvmCross.Forms.Views;assembly=MvvmCross.Forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    x:Class="XamarinFormsWithMvvmCross.Views.MainPage">
    <StackLayout>
        <!-- Place new controls here -->
        <Label 
           Text="{Binding WelcomeText}" 
           HorizontalOptions="Center"
           VerticalOptions="CenterAndExpand" />
    </StackLayout>
</views:MvxContentPage>

That's it for the moment with regards to changes to the XAML files.

In order for our application to run, we need to make some changes to each of the target platforms. By default the Visual Studio Xamarin.Forms template has a significant amount of template code that is added as a starting point for developers. With MvvmCross most of this code is no longer required. We'll go through each platform in turn, starting with UWP.

In the UWP project, change MainPage to inherit from MvxFormsWindowsPage and remove everything except the constructor with a call to InitializeComponent in the code-behind file, MainPage.xaml.cs:

public sealed partial class MainPage
{
  public MainPage()
  {
    InitializeComponent();
  }
}

Add a new class, ProxyMvxApplication, with the following code. This class is required because XAML in UWP can't handle generic base types, so the ProxyMvxApplication abstracts the base class into a non-generic type:

public abstract class ProxyMvxApplication : MvxWindowsApplication<MvxFormsWindowsSetup<Core.App, XFwMvx.App>, Core.App, XFwMvx.App, MainPage>
{
}

Change App.xaml and App.xaml.cs in the UWP project to inherit from ProxyApplication and remove all code except the constructor with the call to InitializeComponent in App.xaml.cs:

sealed partial class App 
{
  public App()
  {
    this.InitializeComponent();
  }
}

With Android, if you see an "Error CS0234 The type or namespace name 'App' does not exist in the namespace," you just need to add a reference to the Mono.Android.Export library, as shown in Figure 6.

Adding Missing Reference to Export Library
[Click on image for larger view.] Figure 6. Adding Missing Reference to Export Library

Setting up the Android project only requires a simple change to the MainActivity class. We're going to rename MainActivity to RootActivity, and update the code as follows:

[Activity(Label = "XFwMvx", Icon = "@mipmap/icon", Theme = "@style/MainTheme",
MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class RootActivity : MvxFormsAppCompatActivity<MvxFormsAndroidSetup<Core.App, App>, Core.App, App>
{
}

Lastly, we need to update the iOS project by adjusting the AppDelegate code as follows:

[Register("AppDelegate")]
public partial class AppDelegate
: MvxFormsApplicationDelegate<MvxFormsIosSetup<XFwMvx.Core.App, XFwMvx.App>, XFwMvx.Core.App, XFwMvx.App>
{
}

With all these changes done, all three platforms are ready. At this point it's important to run each of the applications to make sure they're all setup correctly. Each platform, iOS, Android and UWP, should run and display "Hello Xamarin.Forms with MvvmCross" in the middle of the screen, similar to Figure 7.

Welcome to Xamarin Forms with MvvmCross
[Click on image for larger view.] Figure 7. Welcome to Xamarin Forms with MvvmCross

Now that we've completed all the boilerplate code and setup we can start to leverage the power of both Xmarin.Forms and MvvmCross in order to rapidly build out our application.

As we're going to be displaying a list of contacts, we need some data to work with. For this we're going to create a service, ContactService, that implements IContactService. Both the interface and the implementation will be created in the Core library in a Services folder, and the associated Contact class will be added to a Models folder:

public class Contact
{
  public int Id { get; set; }
  public string Name { get; set; }
}

public interface IContactService
{
  IEnumerable<Contact> FetchContacts();
}

public class ContactService : IContactService
{
  public IEnumerable<Contact> FetchContacts()
  {
    var contacts = new List<Contact>();
    for (int i = 0; i < 1000;="" i++)="" {="" contacts.add(new="" contact="" {="" id="i," name="$"Contact" {i}"="" });="" }="" return="" contacts;="" }="" }="">

To make use of this service, we need to make MvvmCross aware that it exists, and then we need to indicate where in the code it should be used. To add the service into MvvmCross we just need to update the Initialize method of the App class in the Core project:

public override void Initialize()
{
  base.Initialize();

  CreatableTypes()
    .EndingWith("Service")
    .AsInterfaces()
  .RegisterAsLazySingleton();

    RegisterAppStart<MainViewModel>();
}

The bold code iterates over all the types in the current assembly, looking for types that end in "Service." Each type that matches this condition is registered as a singleton for use throughout the application.

Next, we want to update our MainViewModel to make use of this service. All we have to do is modify the default constructor to accept an IContactService argument. MvvmCross is clever enough to look up the registered service that matches that interface and pass it in as a parameter:

private IContactService ContactService { get;  }
public MainViewModel(IContactService contactService)
{
  ContactService = contactService;
}

This service will then be used to populate a list of contacts that we can data bind to. When the MainViewModel is launched, the ViewAppeared method is called, which can be overridden in the MainViewModel in order to load the list of contacts:

public ObservableCollection<Contact> Contacts { get; }= new ObservableCollection<Contact>();
public override void ViewAppeared()
{
  base.ViewAppeared();

  var contacts = ContactService.FetchContacts();
  foreach (var contact in contacts)
  {
    Contacts.Add(contact);
  }
}

Before we get onto building the UI to display the list of contacts, we're going to think through the remainder of the application from a ViewModel perspective. When the user taps on an item in the list, the application should navigate to a new page that will show the details of the selected contact. For this to happen we need to create both a new page as well as the corresponding ViewModel. This is where we'll start, by creating a new ViewModel, ContactDetailsViewModel:

public class ContactDetailsViewModel : MvxViewModel<Contact>
{
  public Contact Contact { get; private set; }
  public override void Prepare(Contact parameter)
  {
    Contact = parameter;
  }
}

The ContactDetailsViewModel inherits from the generic version of MvxViewModel, which includes an abstract method Prepare, which takes a parameter of type Contact. This parameter will be passed from the MainViewModel into the ContactDetailsViewModel during the navigation process. This brings us to how we perform a navigation between pages of the application. Using MvvmCross navigation is done from one ViewModel to another using an instance of the INavigationService, an interface that comes with MvvmCross. Again, we add this as a parameter to the MainViewModel constructor:

private IMvxNavigationService NavigationService { get; }
public MainViewModel(IMvxNavigationService navigationService, IContactService contactService)
{
  NavigationService = navigationService;
  ContactService = contactService;
}

Lastly, we need to expose a method that will be used to trigger navigation:

public Task ShowContactDetails(Contact contact)
{
  return NavigationService.Navigate<ContactDetailsViewModel, Contact>(contact);
}

The final part of building the application is to update the MainPage to display the list of contacts, and to create the ContactDetailsPage. We'll update the MainPage with the following XAML:

<?xml version="1.0" encoding="utf-8" ?>
<views:MvxContentPage
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:views="clr-namespace:MvvmCross.Forms.Views;assembly=MvvmCross.Forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    x:TypeArguments="viewModels:MainViewModel"
    xmlns:viewModels="clr-namespace:XFwMvx.Core.ViewModels;assembly=XFwMvx.Core"
    x:Class="XFwMvx.Views.MainPage">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Label Text="{Binding WelcomeText}" 
           HorizontalOptions="Center"/>
        <ListView ItemsSource="{Binding Contacts}"
                  ItemSelected="ContactSelected"
                  Grid.Row="1"                  >
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <StackLayout>
                            <Label Text="{Binding Id}"/>
                            <Label Text="{Binding Name}"/>
                        </StackLayout>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</views:MvxContentPage>

The MainPage.xaml.cs will need to include the event handler for the ItemSelected event on the ListView:

private void ContactSelected(object sender, Xamarin.Forms.SelectedItemChangedEventArgs e)
{
  ViewModel.ShowContactDetails(e.SelectedItem as Contact);
}

We also need the ContactDetailsPage, which we can add based on the Content Page template, shown in Figure 8.

Adding ContactDetailsPage
[Click on image for larger view.] Figure 8. Adding ContactDetailsPage

On the ContactDetailsPage we'll show some information about the contact:

<?xml version="1.0" encoding="utf-8" ?>
<views:MvxContentPage
    xmlns:views="clr-namespace:MvvmCross.Forms.Views;assembly=MvvmCross.Forms"
    xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XFwMvx.Views.ContactDetailsPage">
    <ContentPage.Content>
        <StackLayout>
            <Label Text="{Binding Contact.Id}"
                VerticalOptions="CenterAndExpand" 
                HorizontalOptions="CenterAndExpand" />
            <Label Text="{Binding Contact.Name}"
                VerticalOptions="CenterAndExpand" 
                HorizontalOptions="CenterAndExpand" />
        </StackLayout>
    </ContentPage.Content>
</views:MvxContentPage> 

With this done, we now have an application which displays a list of contacts and the user can tap through to view the details of the contact.

In this article we walked through creating a new Xamarin.Forms application, adding in MvvmCross, and demonstrated the basic architect of a multi-page application with data being loaded from services. MvvmCross makes connecting new pages and new services a trivial job with the code being well organized and easy to maintain.

The source code for this can be found at my GitHub site.

comments powered by Disqus

Featured

Subscribe on YouTube