Tuesday, 12 May 2015

Creating a Xamarin.Forms app that uses the MVVM pattern

The MVVM pattern is well documented, and is used to cleanly separate the responsibility for the appearance and layout of the UI from the responsibility for the business logic.

In this blog post I’ll explore using Xamarin.Forms and the MVVM pattern to create a simple photo viewer app. For more information about Xamarin.Forms and MVVM, see From Data Bindings to MVVM.

Implementation

Bootstrapping the app

The App class, in the XamarinPhotoViewer project, has the responsibility for bootstrapping the app.

1 public class App : Application
2 {
3 public App()
4 {
5 // The root page of your application
6 MainPage = new MainPage();
7 }
8 }

This is achieved by using the App constructor to set the MainPage property to an instance of the page, in this case MainPage, to be displayed when the app starts.

Connecting view models to views

The simplest approach for connecting a view model to a view is for the view to declaratively instantiate its corresponding view model in XAML. When the view is constructed, the corresponding view model object will also be constructed.

1 <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
2 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
3 xmlns:viewmodels="clr-namespace:XamarinPhotoViewer.ViewModels;
4 assembly=XamarinPhotoViewer"
5 x:Class="XamarinPhotoViewer.Views.MainPage">
6 <ContentPage.BindingContext>
7 <viewmodels:MainPageViewModel />
8 </ContentPage.BindingContext>
9 ...
10 </ContentPage>

When the MainPage view is created by the App class, an instance of the MainPageViewModel class is automatically constructed and set as the view’s BindingContext. This approach requires the view model class to have a parameterless constructor.

An alternative approach is for the view to set its BindingContext in the code-behind file.

1 public partial class PhotoPage : ContentPage
2 {
3 public PhotoPage(object photo)
4 {
5 InitializeComponent();
6 this.BindingContext = new PhotoPageViewModel(photo);
7 }
8 }

The programmatic construction and assignment of the view model within the view’s code-behind has the advantage that it allows parameters to be passed into view model constructors.

Displaying view model data on the view

The MainPage class defines the XAML used to display a collection of photo thumbnails, in a ListView control.

1 <StackLayout>
2 <Label Text="{Binding PageTitle}"
3 HorizontalOptions="Center" />
4 <ListView x:Name="PhotosListView"
5 ItemsSource="{Binding Photos}"
6 ItemTapped="OnItemTapped">
7 <ListView.ItemTemplate>
8 <DataTemplate>
9 <ImageCell ImageSource="{Binding Image}"
10 Text="{Binding Name}" />
11 </DataTemplate>
12 </ListView.ItemTemplate>
13 </ListView>
14 </StackLayout>

The ListView control binds its ItemsSource property to the Photos property on the MainPageViewModel class, which contains a series of photos and corresponding data. Each ListView item is an ImageCell control that displays the photo thumbnail and the photo name. For more information about the ListView control, including controlling the appearance of each item of data displayed by the control, see Working with ListView.

Navigating between pages

Xamarin.Forms provides a built-in navigation model that manages the navigation and user-experience of a stack of pages. This model implements a last-in, first-out stack of pages. For more information, see Navigation.

The XamarinPhotoViewer sample app triggers navigation requests from user interaction in the views. These requests are to navigate between the MainPage and PhotoPage views. When the user taps a ListView item on the MainPage, the PhotoPage is navigated to where the tapped item is displayed along with additional data. The wiring of the ItemTapped event to the OnItemTapped event handler in the view’s code-behind occurs in the view’s XAML.

1 public void OnItemTapped(object sender, ItemTappedEventArgs e)
2 {
3 var photo = e.Item;
4 if (photo == null)
5 return;
6
7 this.Navigation.PushAsync(new PhotoPage(photo));
8 this.PhotosListView.SelectedItem = null;
9 }

The OnItemTapped event handler retrieves the ListView item that was tapped on, and uses the Navigation.PushAsync method to open the PhotoPage, passing in the retrieved item which will be received by the PhotoPage view constructor and passed in turn into the PhotoPageViewModel constructor where it will be used to set view model properties that are bound to from the view. The event handler then resets the selected ListView item in preparation for navigation back to the MainPage view.

On the PhotoPage view, a Button control is used to return to the MainPage view. This Button invokes the OnBackButtonClicked event handler.

1 public void OnBackButtonClicked(object sender, EventArgs e)
2 {
3 this.Navigation.PopAsync();
4 }

This event handler uses the built-in back navigation provided by Xamarin.Forms to return to the previous page.

Summary

The MVVM pattern helps to cleanly separate the responsibility for the appearance and layout of the UI from the responsibility for the business logic. The main advantage of this approach is a reduction in the coupling between business logic and the UI which will make an app easier to test, maintain, and evolve.

The sample app can be downloaded here.

5 comments:

  1. Replies
    1. For some reason, the majority of my OneDrive links no longer work. Here's a new link: https://1drv.ms/u/s!AqqxnsyjuZgbgWORwcnFkoY9dAKF

      Delete
  2. Do you have a pattern for returning data from a Navigation PopAsync to the ViewModel that triggered the PushAsync? In my current case I don't even need to get access to the data, but simply to refresh the viewmodel-->View of the source page?? Make sense?

    ReplyDelete
    Replies
    1. The simplest solution is to use MessagingCenter (or something similar, Prism's event aggregation service) to publish an event when the PopAsync call is made, and subscribe to that event in your VM and handle the event to refresh the page.

      From a clean architecture point of view, it maybe a good idea to abstract MessagingCenter if using it from your ViewModel.

      Delete
  3. Thanks for the direction. I had implemented a solution that involved the OnAppearing event which I don't really like. The MessagingCenter pattern sounds like the way to go. I will give it a try.

    ReplyDelete