PetrolTracker MVVM
You Will Learn
- How to connect view models to views by using a view model locator.
- How to use dependency injection to decouple concrete types from the code that depends on the types.
- How a view is updated in response to changes in the underlying view model.
- How to save and restore page state.
- How to invoke commands from a view.
- How to invoke navigation from a view.
- How to unit test view model functionality.
Applies To
- Silverlight for Windows Phone OS 7.1
The PetrolTracker MVVM application enables a user to track the petrol consumption of three sample vehicles. The application follows the MVVM pattern where UI gestures on the view are forwarded to the view model via commands. When a command is invoked, a method in a view model is executed. Each method performs the required operation and often updates a view model property that the view binds to.
The application enables dependency injection and testability by abstracting static Windows Phone 7.1 SDK APIs and non-static APIs that lack interfaces.
The Contents of the PetrolTracker.MVVM Project
The PetrolTracker.MVVM project organizes the source code and other resources into folders. The following table outlines what is contained in each folder.
Project folder |
Description |
---|---|
Root |
In the root folder of the project, you'll find the App.xaml file that every Windows Phone application project must include. This defines some of the startup behavior of the application. The root folder also contains some image files that all Windows Phone applications must include. |
Properties |
In this folder, you'll find two manifest files, the AssemblyInfo.cs file, and a resource dictionary used at design time. The WMAppManifest.xml file describes the capabilities of the application and specifies the initial page to display when the user launches the application. |
Resources |
This folder contains various image files that the application uses. |
Services |
This folder contains interfaces and classes that provide services to the application. This includes the ContainerService class, which creates the registrations and mappings in the Funq dependency injection container. In addition, the folder also contains the DataService class, which retrieves data from isolated storage, and the DialogService class, which provides a service-level abstraction over the MessageBox class. |
Toolkit.Content |
This folder contains images used on the application bar. |
View |
This folder contains the views that define the pages in the application. |
ViewModel |
This folder contains view models. |
Dependency Injection
PetrolTracker uses a dependency injection container to manage the instantiation of the view model and service classes in the application. PetrolTracker uses the Funq dependency injection container, which is lightweight and very fast.
Dependency injection enables decoupling of concrete types from the code that depends on these types. It uses a container that holds a list of registrations and mappings between interfaces and abstract types and the concrete types that implement or extend these types.
The following code example shows how the ContainerService class creates the registrations and mappings in the Funq dependency injection container.
public class ContainerService : IDisposable
{
…
/// <summary>
/// Gets the container.
/// </summary>
public Container Container { get; private set; }
/// <summary>
/// Initializes a new instance of the
/// <see cref="T:PetrolTracker.Mvvm.Services.ContainerService"/> class.
/// </summary>
public ContainerService()
{
this.Container = new Container();
this.ConfigureContainer();
}
…
void ConfigureContainer()
{
this.Container.Register<INavigationServiceFacade>(
c => new NavigationServiceFacade(((App)
Application.Current).RootFrame));
this.Container.Register<IIsolatedStorageFacade>(
c => new IsolatedStorageFacade());
this.Container.Register<IDataService>(
c => new DataService(c.Resolve<IIsolatedStorageFacade>()));
this.Container.Register<IDialogService>(
c => new DialogService());
this.Container.Register(c => new VehicleHistoryViewModel(
c.Resolve<IDataService>(),
c.Resolve<INavigationServiceFacade>(),
c.Resolve<IIsolatedStorageFacade>()
)).ReusedWithin(ReuseScope.None);
…
}
}
In PetrolTracker, the ViewModelLocator class instantiates the ContainerService object and is the only class in the application that holds a reference to a ContainerService object. By default, the Funq dependency injection container registers instances as shared components. This means that the container will cache the instance on behalf of the application, with the lifetime of the instance then being tied to the lifetime of the container. However, view model classes are registered so that each request to resolve the dependency will result in a new instance being returned.
Connecting View Models to Views
There are several approaches that can be used to connect a view model to a view, including direct relations and data template relations. PetrolTracker uses a view model locator because this approach means that the application has a single class that is responsible for connecting view models to views. This still leaves developers free to choose to manually perform the hookup within the view model locator, or by using a dependency injection container. The following illustration shows the relationships between the view, view model locator, and container locator.
PetrolTracker uses the view model locator approach to connect the view to a view model. This approach works well for applications with a limited number of pages (which is often the case with Windows Phone applications), but is not always appropriate in larger applications.
The following code example from the App.xaml file shows how the view model locator class is identified and made available to the application. The application declares the ViewModelLocator class in the <Application.Resources> section of the App.xaml file.
<Application
xmlns:viewModel="clr-namespace:PetrolTracker.Mvvm.ViewModel"
x:Class="PetrolTracker.Mvvm.App"
… >
<Application.Resources>
…
<viewModel:ViewModelLocator x:Key="ViewModelLocator"/>
…
</Application.Resources>
…
</Application>
The following code example from the VehicleHistoryView.xaml file shows how a view can then reference the ViewModelLocator class as a static resource in its data context bindings.
<phone:PhoneApplicationPage
x:Class="PetrolTracker.Mvvm.View.VehicleHistoryView"
DataContext="{Binding Source={StaticResource ViewModelLocator},
Path=VehicleHistoryViewModel}"
…
</phone:PhoneApplicationPage>
The Path attribute identifies the property of the ViewModelLocator instance that returns the view model associated with the current page.
The following code example shows the parts of the ViewModelLocator class that return the VehicleHistoryViewModel instance.
public class ViewModelLocator : IDisposable
{
readonly ContainerService containerService;
…
/// <summary>
/// Gets the vehicle history view model.
/// </summary>
public VehicleHistoryViewModel VehicleHistoryViewModel
{
get { return containerService.Container
.Resolve<VehicleHistoryViewModel>(); }
}
…
}
Notice how the instance of the ContainerService class exposes the Funq Container property to resolve the view model.
The ViewModelLocator class connects views to view models.
Accessing the Model
The model classes that represent the domain entities used in the application, can be accessed through the DataService class. The illustration below shows the key model classes and the relationships between them.
The DataService class contains a property named Fleet, of type Fleet. This can be used to access the properties, events, and methods of the Fleet class. The Fleet class contains a property named CurrentVehicle, of type Vehicle, which can be used to access the properties and event of the Vehicle class. The Fleet class also contains a collection named Vehicles, which is of type IList<Vehicle>. This collection contains the three sample vehicles that are used by the application. The Vehicle class contains an ObservableCollection of type Fillup, named Fillups, which can be used to access the properties and methods of the FillUp class.
Application Launching and Closing Events
The Windows Phone execution model governs the lifecycle of applications running on a Windows Phone, from when the application is launched until it is terminated. For more information about the Windows Phone execution model see Execution Model Overview for Windows Phone.
The following code example shows the Application_Launching event handler method, which is called when a new application instance is launched by the user.
void Application_Launching(object sender, LaunchingEventArgs e)
{
…
if (!IsolatedStorageSettings.ApplicationSettings.Contains(Constants.FleetKey))
{
var dataGenerator = new DataGenerator();
var fleet = dataGenerator.CreateFleet();
fleet.SetCurrentVehicle(fleet.Vehicles[0].VehicleId);
IsolatedStorageSettings.ApplicationSettings.Add(Constants.FleetKey, fleet);
IsolatedStorageSettings.ApplicationSettings.Save();
}
}
The Application_Launching event handler examines the application settings, and if it does not contain the Fleet constant, a new instance of the DataGenerator class is created, and the CreateFleet method is called on this class. The DataGenerator class creates a fleet of three sample vehicles and copies each vehicle's data to isolated storage. The current vehicle is set to the first item in the Vehicles collection, and then the IsoaltedStorageSettings dictionary is updated.
To meet the Windows Phone certification requirements, applications must complete application lifecycle events within 10 seconds.
The following code example shows the Application_Closing event handler method, which is called when the user navigates backwards past the first page of an application.
void Application_Closing(object sender, ClosingEventArgs e)
{
// Ensure that required application state is persisted here.
// This call starts the chain of object disposing
((ViewModelLocator) this.Resources["ViewModelLocator"]).Dispose();
}
This event handler simply frees up memory used by the application by calling the Dispose method of the ViewModelLocator object. This disposes the container, which in turn disposes of each view model and each service because once the ViewModelLocator object and services are disposed, the references to the view models are lost and hence can be reclaimed by the garbage collector.
The User Interface
The PetrolTracker MVVM implementation follows the UI design guidance published in the User Experience Design Guidelines for Windows Phone.
The User Experience Design Guidelines for Windows Phone describes best practices for designing the UI of a Windows Phone application.
The UI consists of three views, with each view containing resources and controls.
- AddFillUpView is used to add fill-up data for a vehicle.
- SettingsView is used to select a vehicle for which to display fill-up data.
- VehicleHistoryView displays the fill-up data for a selected vehicle.
The VehicleHistory view is displayed when the application first starts. The following code example shows how the application is configured to display the VehicleHistory view to the user.
<Tasks>
<DefaultTask Name="_default" NavigationPage="/View/VehicleHistory.xaml" />
</Tasks>
The WMAppManifest.xml file sets the NavigationPage property of the DefaultTask element to the VehicleHistory view. This view is the first page that you will be shown.
VehicleHistoryView
The VehicleHistoryView class uses a Pivot containing two PivotItems, to display data. The first PivotItem contains two Images, while the second PivotItem contains a ListBox of the fill-up data. The following code example shows how the PivotItems are defined.
<controls:Pivot SelectedIndex="{Binding Path=SelectedPivotPage, Mode=TwoWay}"
x:Name="vehicleHistoryPivot" …>
<controls:PivotItem Header="charts" Margin="0">
<Grid>
…
<Image x:Name="mpgHistoryChart"
Source="/PetrolTracker.Mvvm;component/Resources/Images/MPG.png"
Visibility="Collapsed"/>
<Image x:Name="costHistoryChart"
Source="/PetrolTracker.Mvvm;component/Resources/Images/Cost.png"
Visibility="Collapsed"/>
<Grid Grid.Row="1" Margin="12,0" >
…
<RadioButton x:Name="btnMpg" …
Command="{Binding MpgChartSelectedCommand" />
<RadioButton x:Name="btnCost" …
Command="{Binding CostChartSelectedCommand}" />
</Grid>
</Grid>
</controls:PivotItem>
<controls:PivotItem Header="fill-ups">
<Grid Margin="6,10,0,0">
…
<ListBox
Grid.Row="1"
ItemContainerStyle="{StaticResource ListBoxStyle}"
ItemsSource="{Binding Source={StaticResource fillUpsCvs}}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock … Text="{Binding DatePurchased, …}" … />
<TextBlock … Text="{Binding QuantityUnitsPurchased}" />
<TextBlock … Text="{Binding MilesDriven}" />
<TextBlock … Text="{Binding PricePerUnitPurchased, … }" />
<TextBlock … Text="{Binding Mpg, … }" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</controls:PivotItem>
</controls:Pivot>
The first PivotItem, containing the Images, also contains two re-styled RadioButtons. Each RadioButton binds to a command that will execute a method when the button is tapped. Each image presents a sample chart of a different filtered view of the raw data. The first sample chart filters data based upon MPG, and the second sample chart filters data based upon cost. The data source for the ListBox is a CollectionViewSource named fillupCvs. The following code example shows how it is defined as a page-level Resource.
<phone:PhoneApplicationPage
… >
<phone:PhoneApplicationPage.Resources>
<CollectionViewSource x:Key="fillUpsCvs"
Source="{Binding Path=FilteredFillUps}" />
…
</phone:PhoneApplicationPage.Resources>
…
</phone:PhoneApplicationPage>
Notice that the Source property of the CollectionViewSource binds to the FilteredFillUps property in the VehicleHistoryViewModel class.
AddFillUpView
The AddFillUpView class uses a DatePicker and three TextBox instances to accept vehicle fill-up data from the user. The following code example shows how these controls are defined.
<toolkit:DatePicker Value="{Binding Path=PurchaseDate, Mode=TwoWay}" … />
<TextBox Text="{Binding Path=Odometer, Mode=TwoWay}" … >
<Interactivity:Interaction.Behaviors>
<prism:UpdateTextBindingOnPropertyChanged/>
</Interactivity:Interaction.Behaviors>
</TextBox>
<TextBox Text="{Binding Path=Gallons, Mode=TwoWay}" … >
<Interactivity:Interaction.Behaviors>
<prism:UpdateTextBindingOnPropertyChanged/>
</Interactivity:Interaction.Behaviors>
</TextBox>
<TextBox Text="{Binding Path=PricePerGallon, Mode=TwoWay}" … >
<Interactivity:Interaction.Behaviors>
<prism:UpdateTextBindingOnPropertyChanged/>
</Interactivity:Interaction.Behaviors>
</TextBox>
Each control binds to a property in the AddFillUpViewModel class, and uses the UpdateTextBindingOnPropertyChanged behavior from the Prism library. This behavior ensures that the view always notifies the view model of any changes in the TextBox control as soon as they happen.
SettingsView
The SettingsView class uses a ListBox to display the three sample vehicles that the user can select from. The following code example shows the ListBox and the DataTemplate that it uses.
<ListBox ItemsSource="{Binding Path=Vehicles}"
SelectedItem="{Binding Path=SelectedVehicle}" …
x:Name="FleetVehicles" …>
…
<ListBox.ItemTemplate>
<DataTemplate>
<Grid …>
<Image … Source="{Binding Picture}"/>
<TextBlock … Text="{Binding VehicleName}" …/>
<TextBlock … Text="{Binding AverageMpg, …}" …/>
<TextBlock … Text="{Binding CurrentOdometer, …}" …/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction
Command="{Binding Path=DataContext.VehicleSelectedCommand,
ElementName=FleetVehicles}"
CommandParameter="{Binding Path=SelectedItem,
ElementName=FleetVehicles}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
The ListBox binds to the Vehicles property in the SettingsViewModel class. The Vehicles property is a collection of type IList<Vehicle>. The DataTemplate contains an Image and TextBlocks which bind to properties contained in the Vehicle model class through the Vehicles property in the SettingsViewModel class.
In order to handle the SelectionChanged event in the ListBox, an EventTrigger is used. An EventTigger specifies the Action that will be called when the SelectionChanged event is raised on the ListBox. The InvokeCommandAction class binds a command to the SelectionChanged event so that when the event is raised, the action associated with the VehicleSelectedCommand in the SettingsViewModel class is executed. The SelectedItem in the ListBox is passed to the VehicleSelectedCommand as a parameter.
Updating the Views
Views can automatically update the values they display in response to changes in the underlying view model if the view model implements the INotifyPropertyChanged interface. In PetrolTracker, each view model inherits from the NotificationObject class in the Prism library. The NotificationObject class implements the INotifyPropertyChanged interface. The Vehicle model class also uses the ObservableCollection class from the System.Collections.ObjectModel namespace that also implements the INotifyPropertyChanged interface, as well as the INotifyCollectionChanged interface.
The view models implement the INotifyPropertyChanged interface indirectly through the NotificationObject class from the Prism Library.
Saving and Restoring Page State
In PetrolTracker, each view model is responsible for persisting and reloading its own state. Therefore, when a page is navigated away from, the state of the UI stored in the active view model is saved to page state, and when that page is navigated to, the UI's state is restored. Therefore, when the operating system deactivates the application, the state of the UI stored in the active view model is saved. When the operating system reactivates the application, the operating system will redisplay the view that was active when the application was deactivated. When returning from dormancy, all object instances will still be in memory in the exact state they were in prior to dormancy, so the application does not need to perform any additional tasks. However, when returning from a tombstoned state, the view model locator will instantiate the view model for the view, and the view model will restore its saved state and initialize any services so that it is back in the state it was in when it was originally deactivated.
The page-level OnNavigatedTo and OnNavigatedFrom event handlers are used to call methods in the appropriate view model to handle the persistence and restoration of UI state. The following code example shows how the event handlers in the AddFillUpView class call methods in the AddFillUpViewModel class.
AddFillUpViewModel addFillUpViewModel;
AddFillUpViewModel AddFillUpViewModel
{
get { return addFillUpViewModel ??
(addFillUpViewModel = this.DataContext as AddFillUpViewModel); }
}
…
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if(this.AddFillUpViewModel != null)
{
bool isBeingActivated = PhoneApplicationService.Current.StartupMode ==
StartupMode.Activate;
this.AddFillUpViewModel.NavigatedTo(isBeingActivated, this.State);
}
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
// if not navigating back, save page state.
if (e.NavigationMode == NavigationMode.Back) return;
if(this.AddFillUpViewModel != null)
{
this.AddFillUpViewModel.NavigatedFrom(this.State);
}
}
The OnNavigatedTo method calls the NavigatedTo method in the AddFillUpViewModel class and passes in a Boolean value that indicates whether or not the application was launched by the user or the user is returning to the application by pressing the Back button, and the page's State dictionary. The OnNavigatedFrom method calls the NavigatedFrom method in the AddFillUpViewModel class and passes in the page’s State dictionary.
The AddFillUpViewModel saves state to the page-level State dictionary which stores key/value pairs where the values are primitive value types or serializable types. The following code example shows how the AddFillUpViewModel class saves and restores its state.
const string PurchaseDateKey = "PurchaseDateKey";
const string OdometerKey = "OdometerKey";
const string GallonsKey = "GallonsKey";
const string PricePerGallonKey = "PricePerGallonKey";
bool isNewInstance;
…
public void NavigatedTo(bool isBeingActivated, IDictionary<string, object> state)
{
// this demonstrates page settings that survives tombstonning
if (isNewInstance && isBeingActivated && state.Count > 0)
{
var pageStateHelper = new PageStateHelper(state);
this.PurchaseDate = pageStateHelper.GetValue<DateTime?>(PurchaseDateKey,
DateTime.Now);
this.Odometer = pageStateHelper.GetValue(OdometerKey, string.Empty);
this.Gallons = pageStateHelper.GetValue(GallonsKey, string.Empty);
this.PricePerGallon = pageStateHelper.GetValue(PricePerGallonKey,
string.Empty);
CalculateReadOnlyFields();
}
isNewInstance = false;
}
public void NavigatedFrom(IDictionary<string, object> state)
{
state[PurchaseDateKey] = this.PurchaseDate;
state[OdometerKey] = this.Odometer;
state[GallonsKey] = this.Gallons;
state[PricePerGallonKey] = this.PricePerGallon;
}
The NavigatedTo method is called by the view's OnNavigatedTo event handler when the page is navigated to. It examines the isNewInstance and isBeingActivated flags, and if both flags are true and the state.Count property is greater than zero, it means that the application is returning from a tombstoned state, in which case page-level state must be restored. In addition, this method will also get called when navigating page to page, in which case the UI does not need to be updated from page state. If the application is returning from a tombstoned state, the view model properties that the view binds to are restored with the help of the PageStateHelper class. Finally, the isNewInstance flag is set to false, to indicate that the application is no longer a new instance.
The NavigatedFrom method is called by the view's OnNavigatedFrom event handler when the page is navigated away from. Provided that the user is not navigating backwards, the view model properties that the view binds to are stored in the State dictionary.
The PageStateHelper class, in the PetrolTracker.Common project, is used to persist and retrieve any state that the page needs when it's navigated to. The following code example shows the complete PageStateHelper class.
public class PageStateHelper
{
readonly IDictionary<string, object> state;
/// <summary>
/// Initializes a new instance of the <see cref="PageStateHelper"/> class.
/// </summary>
/// <param name="state">The page state dictionary.</param>
public PageStateHelper(IDictionary<string, object> state)
{
if (state == null) throw new ArgumentNullException("state");
this.state = state;
}
/// <summary>
/// Gets the value.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key">The key.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns>Saved instance of T or the defaultValue</returns>
public T GetValue<T>(string key, T defaultValue)
{
object value;
if (state.TryGetValue(key, out value))
{
if (value is T)
{
return (T) value;
}
}
return defaultValue;
}
}
The PageStateHelper class provides a GetValue<T> method that retrieves the specified object from the page state object.
This implementation provides basic page-state support, but does not store every aspect of the page state. Specifically, this implementation does not store a value that indicates which text box has focus, nor does it store the cursor position or selection state of the focused text box. The importance of saving this state information depends on the application, but for implementation suggestions, see How to: Preserve and Restore Page State for Windows Phone.
Invoking Commands from a View
In a Windows Phone Silverlight application, you can invoke some action in response to a user action (such as a button tap) by creating an event handler in the code-behind class. However, in the MVVM pattern, the responsibility for implementing the action lies with the view model, and you should avoid placing code in the code-behind classes. Therefore, you should connect the control to a method in the view model using a command binding.
In Silverlight for Windows Phone, the ButtonBase class and the Hyperlink class both support Command and CommandParameter properties. The Command property can reference an ICommand implementation that comes from a view model data source, through a {Binding} usage. The command is then interpreted at run time by the Silverlight input system. For more information, see ButtonBase.Command Property, Hyperlink.Command Property, and ICommand Interface.
However, because the ApplicationBarIconButton class does not extend the ButtonBase class, PetrolTracker uses the ApplicationBarButtonCommand behavior from the Prism Library to bind the click event of the ApplicationBarButtonCommand to the execution of a command.
Windows Phone controls that derive from ButtonBase or Hyperlink support binding to ICommand instances.
PetrolTracker uses bindings and a custom behavior to associate actions in the UI with commands in the view model. The following code example from the AddFillUpView.xaml file shows how the Save button on the application bar is associated with a command.
<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar IsVisible="True">
<shell:ApplicationBarIconButton
IconUri="/Resources/Images/appbar.save.rest.png" Text="save" />
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>
<Custom:Interaction.Behaviors>
<prismInteractivity:ApplicationBarButtonCommand ButtonText="save"
CommandBinding="{Binding Path=SaveCommand}" />
</Custom:Interaction.Behaviors>
The attributes attached to the ApplicationBarIconButton controls (Text and IconUri) only affect their appearance. The ApplicationBarButtonCommand elements handle the connection to the commands; they identify the control to associate with the command through the ButtonText attribute and the command through the CommandBinding attribute.
The ApplicationBarButtonCommand class from the Prism Library implements the custom behavior that links a button click in the UI to the SaveCommand property in the AddFillUpViewModel class.
The SaveCommand property uses an instance of the DelegateCommand class that implements the ICommand interface. The following code example from the AddFillUpViewModel class shows the definition of the SaveCommand property.
ICommand saveCommand;
…
public ICommand SaveCommand
{
get { return saveCommand ?? (
saveCommand = new DelegateCommand(SaveExecute)); }
}
Note
For more information about the DelegateCommand class from Prism, see Chapter 9 of the Prism documentation, Communicating Between Loosely Coupled Components.
The following code example from the AddFillUpViewModel class shows the implementation of the SaveExecute method.
void SaveExecute()
{
var errors = new List<string>();
int odometerReading;
double pricePerUnitPurchased;
double quantityUnitsPurchased;
// sanity check for nulls, should not happen.
if (!this.PurchaseDate.HasValue)
{
errors.Add("Purchase date is required.");
}
…
var fillUp = new FillUp
{
DatePurchased = this.PurchaseDate.Value,
MilesDriven = odometerReading –
dataService.Fleet.CurrentVehicle.CurrentOdometer,
OdometerReading = odometerReading,
PricePerUnitPurchased = pricePerUnitPurchased,
QuantityUnitsPurchased = quantityUnitsPurchased
};
…
}
Therefore, when a user taps the Save ApplicationBarIconButton in the AddFillUpView page, the SaveExecute method in the AddFillUpViewModel is executed.
Invoking Navigation Requests from a View
In addition to invoking commands from the view, PetrolTracker also triggers navigation requests from the view. These requests could be to navigate to a particular view or navigate back to the previous view. In some scenarios, for example if the application needs to navigate to a new view when a command completes, the view model will need to send a message to the view. In other scenarios, you might want to trigger the navigation request directly from the view without involving the view model directly. When you're using the MVVM pattern, you want to be able to do all this without using any code-behind in the view, and without introducing any dependency on the view implementation in the view model classes.
The following code example from the VehicleHistoryView.xaml file shows how navigation is initiated in PetrolTracker.
<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar IsVisible="True">
<shell:ApplicationBarIconButton
IconUri="/Resources/Images/appbar.feature.settings.rest.png"
Text="settings" />
<shell:ApplicationBarIconButton
IconUri="/Resources/Images/appbar.add.rest.png" Text="add fill-up" />
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>
<Custom:Interaction.Behaviors>
<prismInteractivity:ApplicationBarButtonCommand ButtonText="settings"
CommandBinding="{Binding Path=SettingsCommand}" />
<prismInteractivity:ApplicationBarButtonCommand ButtonText="add fill-up"
CommandBinding="{Binding Path=AddFillUpCommand}"/>
</Custom:Interaction.Behaviors>
In both cases, commands are invoked in the view model. The code that implements each command causes the application to navigate to a specific view, so the navigation is initiated from the view model. The following code example from the VehicleHistoryViewModel class illustrates this.
readonly INavigationServiceFacade navigationServiceFacade;
ICommand addFillUpCommand;
ICommand settingsCommand;
…
public ICommand AddFillUpCommand
{
get { return addFillUpCommand ??
(addFillUpCommand = new DelegateCommand(AddFillUpExecute)); }
}
…
public ICommand SettingsCommand
{
get { return settingsCommand ??
(settingsCommand = new DelegatCommand(SettingsExecute)); }
}
public VehicleHistoryViewModel(IDataService dataService,
INavigationServiceFacade navigationServiceFacade,
IIsolatedStorageFacade isolatedStorageFacade)
{
…
if (navigationServiceFacade == null)
throw new ArgumentNullException("navigationServiceFacade");
…
this.navigationServiceFacade = navigationServiceFacade;
…
}
…
void AddFillUpExecute()
{
navigationServiceFacade.Navigate(new Uri("/View/AddFillUpView.xaml",
UriKind.Relative));
}
void SettingsExecute()
{
navigationServiceFacade.Navigate(new Uri("/View/SettingsView.xaml",
UriKind.Relative));
}
Navigation is performed using the NavigationServiceFacade class, from the PetrolTracker.MVVM.Adapters project, shown in the following code example.
public class NavigationServiceFacade : INavigationServiceFacade
{
readonly PhoneApplicationFrame frame;
/// <summary>
/// Gets a value that indicates whether there is at least one entry in the back
/// navigation history.
/// </summary>
public bool CanGoBack
{
get { return frame.CanGoBack; }
}
/// <summary>
/// Gets the uniform resource identifier (URI) of the content that is currently
/// displayed.
/// </summary>
public Uri CurrentSource
{
get { return frame.CurrentSource; }
}
/// <summary>
/// Initializes a new instance of the <see cref="NavigationServiceFacade"/>
/// class.
/// </summary>
/// <exception cref="ArgumentNullException">If frame is null.</exception>
/// <param name="frame">The frame.</param>
public NavigationServiceFacade(PhoneApplicationFrame frame)
{
if (frame == null) throw new ArgumentNullException("frame");
this.frame = frame;
}
/// <summary>
/// Navigates to the content specified by the uniform resource identifier (URI).
/// </summary>
/// <param name="source">The URI of the content to navigate to.</param>
/// <returns>Returns bool. True if the navigation started successfully;
/// otherwise, false.</returns>
public bool Navigate(Uri source)
{
return frame.Navigate(source);
}
/// <summary>
/// Navigates to the most recent entry in the back navigation history, or throws
/// an exception if no entry exists in back navigation.
/// </summary>
public void GoBack()
{
frame.GoBack();
}
}
This class, which implements the INavigationServiceFacade interface, uses the PhoneApplicationFrame instance to perform the navigation request for the application.
Using the PhoneApplicationFrame instance ensures that the phone maintains the correct navigation stack for the application so that navigating backward works in the way that users expect.
A view model can invoke the Navigate method on the NavigationServiceFacade object to cause the application to navigate to a particular view in the application or the GoBack method to return to the previous view.
Each view model maintains its own INavigationServiceFacade instance, and the Funq dependency injection container is responsible for initially creating the NavigationServiceFacde object that implements this interface.
Displaying User Interface Notifications
PetrolTracker uses message boxes to provide error notifications that inform the user that some expected action will not occur.
You should follow the guidance published in the User Experience Design Guidelines for Windows Phone when you design the notification system for your application.
The following code example shows how the AddFillUpViewModel displays a message box when the save process is unable to validate the data entered by the user.
readonly IDialogService dialogService;
…
public AddFillUpViewModel(IDataService dataService,
INavigationServiceFacade navigationServiceFacade,
IIsolatedStorageFacade isolatedStorageFacade,
IDialogService dialogService)
{
…
if (dialogService == null) throw new ArgumentNullException("dialogService");
…
this.dialogService = dialogService;
…
}
…
void SaveExecute()
{
var errors = new List<string>();
int odometerReading;
double pricePerUnitPurchased;
double quantityUnitsPurchased;
// sanity check for nulls, should not happen.
if (!this.PurchaseDate.HasValue)
{
errors.Add("Purchase date is required.");
}
// locate any type mismatch errors, example an "a" in a numeric field.
// note: this is not object validation
if (!int.TryParse(this.Odometer, out odometerReading))
{
errors.Add("Odometer is not a valid whole number.");
}
if (!double.TryParse(this.Gallons, out quantityUnitsPurchased))
{
errors.Add("Gallons is not a valid number.");
}
if (!double.TryParse(this.PricePerGallon, out pricePerUnitPurchased))
{
errors.Add("Price per gallon is not a valid number.");
}
if (errors.Count > 0)
{
dialogService.Show(string.Join(Environment.NewLine, errors.ToArray()),
"Invalid Input", DialogButton.OK);
return;
}
…
}
The constructor for the AddFillUpViewModel accepts a number of parameters, including one of type IDialogService. Therefore, an instance of a class that implements IDialogService is passed into the constructor, and the dialogService field is set to this instance.
The SaveExecute method defines a collection named errors, which will contain a list of any validation errors stemming from the user-entered data. It then attempts to parse each value entered in the AddFillupView page to its correct type. If the value does not parse, an error message is added to the errors collection. Following an attempt to parse all the entered values, the user is shown a message box informing the user of the errors encountered, provided that the errors collection contains any items. A call to the Show method in the DialogService class displays the message box.
The following code example shows how the DialogService wraps the MessageBox class from the Windows Phone 7.1 SDK.
public class DialogService : IDialogService
{
…
/// <summary>
/// Displays a message box that contains the specified text,
/// title bar caption, and response buttons.
/// </summary>
/// <param name="message">The message to display.</param>
/// <returns>Returns DialogResult.OK.</returns>
public DialogResult Show(string message)
{
MessageBox.Show(message);
return DialogResult.OK ;
}
…
}
The DialogService class provides a Show method that simply calls the MessageBox.Show method from the Windows Phone 7.1 SDK. This approach enables testability by abstracting static Windows Phone 7.1 SDK classes.
Abstracting Windows Phone 7.1 SDK Classes
There are few public interfaces or abstract classes available in the Windows Phone 7.1 SDK. Therefore, interfaces were produced for some of the classes in the Windows Phone 7.1 SDK that are used by the PetrolTracker MVVM implementation. Facades were then created, which are wrapper classes that provide a simplified interface for a class. The facade translates calls to its interface into calls to the original class, thus enabling writing loosely coupled testable code.
A facade is a design pattern that provides a simplified interface for a class. The facade translates calls to its interface into calls to the original class. This approach enables writing loosely coupled testable code.
The following illustration shows how the NavigationServiceFacade class provides a facade over the PhoneApplicationFrame SDK class.
The NavigationServiceFacade class implements the INavigationServiceFacade interface. At run time, the PetrolTracker MVVM implementation uses the NavigationServiceFacade class directly, rather than the PhoneApplicationFrame class.
A service is a facade that exposes a loosely coupled unit of functionality that implements one action.
The right-hand side of the illustration shows the MockNavigationServiceFacade class, which also implements the INavigationServiceFacade interface and is used in unit tests instead of the NavigationServiceFacade class. This gives you the ability to unit test business logic that needs to interact with the PhoneApplicationFrame.
The following facades are provided by the PetrolTracker MVVM implementation, and can be found in the PetrolTracker.MVVM.Adapters project.
Facade |
Interface |
Abstracts |
---|---|---|
IsolatedStorageFacade |
IIsolatedStorageFacade |
IsolatedStorageSettings IsolatedStorageFile |
NavigationServiceFacade |
INavigationServiceFacade |
PhoneApplicationFrame |
Unit Testing MVVM Applications
One of the benefits of combining the MVVM pattern with the dependency injection pattern is that it promotes the testability of the application, making it easy to create tests that exercise the view model. Unit tests differ from integration or user acceptance tests in that they test the smallest unit of functionality possible. Typically this is the behavior of a method.
Unit tests should focus on how the code under test functions in response to values returned by dependent systems. By using mocks of dependent systems, the return values or exceptions to be thrown can easily be controlled. For more information see Exploring the Continuum of Test Doubles.
The PetrolTracker.MVVM.Tests project contains mock implementations of the Windows Phone 7.1 SDK service and facade classes used by the PetrolTracker MVVM implementation. The following code example shows the MockDataService class.
public class MockDataService : IDataService
{
public Fleet Fleet { get; set; }
public MockDataService()
{
this.Fleet = new Fleet();
}
}
The MockDataService class implements the IDataService interface, with the constructor simply setting the Fleet property to be a new instance of a Fleet object. The Fleet property can then be queried from outside the mock.
The following code example shows the MockNavigationServiceFacade class.
public class MockNavigationServiceFacade : INavigationServiceFacade
{
public Uri NavigateCalledWithUri { get; set; }
public bool GoBackCalled { get; set; }
public bool CanGoBack { get; set; }
public Uri CurrentSource
{
get { throw new NotImplementedException(); }
}
public bool Navigate(Uri source)
{
this.NavigateCalledWithUri = source;
return true;
}
public void GoBack()
{
this.GoBackCalled = true;
}
}
The MockNavigationServiceFacade class implements the INavigationServiceFacade interface, following a pattern whereby the methods set a public property that can then be queried from outside the mock.
Unit tests can then be written that use the mocks to test aspects of the view model business logic. The following code example shows the SelectingVehicleSetFleetCurrentVehicleAndNavigatesBack test method, which follows the standard arrange, act, assert pattern. The unit test validates that a vehicle can be selected by the SettingsViewModel, and that after selecting a vehicle, the CurrentVehicle property of the Fleet object is set to the selected vehicle and then the navigation service will call the GoBack method to return the user to the previous page.
[TestMethod]
public void SelectingVehicleSetFleetCurrentVehicleAndNavigatesBack()
{
// arrange
var targetVehicle = new Vehicle { VehicleId = Guid.NewGuid() };
var extraVehicle = new Vehicle { VehicleId = Guid.NewGuid() };
var dataService = new MockDataService();
var navigationService = new MockNavigationServiceFacade
{ GoBackCalled = false };
var target = new SettingsViewModel(dataService, navigationService);
dataService.Fleet.Vehicles.Add(targetVehicle);
dataService.Fleet.Vehicles.Add(extraVehicle);
// act
target.VehicleSelectedCommand.Execute(targetVehicle);
// assert
Assert.AreSame(targetVehicle, dataService.Fleet.CurrentVehicle);
Assert.IsTrue(navigationService.GoBackCalled);
}
The test first creates two new Vehicle objects—targetVehicle and extraVehicle—before creating new instances of the MockDataService and the MockNavigationServiceFacade. When creating the instance of the MockNavigationServiceFacade class, the GoBackCalled property is initialized to false. Then, a new instance of the SettingsViewModel class is created, which accepts the two instances of the mocks as parameters, before the instance of the Vehicle objects are added to the Fleet instance in the MockDataService. The VehicleSelectedCommand from the SettingsViewModel class is then executed in order to select a specific vehicle and navigate the user to the previous page. The test method then checks whether the CurrentVehicle in the Fleet instance in the MockDataService is identical to the selected vehicle before examining the GoBackCalled property in the MockNavigationServiceFacade instance in order to see whether the GoBack method was called.
Running Unit Tests
PetrolTracker uses the Silverlight unit test framework for Windows Phone and Silverlight 4. The Windows Phone project named PetrolTracker.MVVM.Tests contains the unit tests for the PetrolTracker.MVVM application. To run the tests, first build and then deploy the project either to the Windows Phone emulator or to a real device. On the Windows Phone device, you can then launch the application named PetrolTracker.MVVM.Tests, and then select the unit tests you want to run.
PetrolTracker uses a Silverlight unit-testing framework to run unit tests on the phone emulator and on real devices.
Summary
The PetrolTracker MVVM application enables a user to track the petrol consumption of three sample vehicles. The application follows the MVVM pattern of UI gestures on the view being forwarded to the view model via commands. When a command is invoked, a method in a view model is executed. Each method performs the required operation and often updates a view model property that the view binds to. Separating the UI from the business logic helps to make an application easier to test, maintain, and evolve.
The application enables dependency injection and testability by abstracting static Windows Phone 7.1 SDK APIs and non-static APIs that lack interfaces. Combining the MVVM pattern with the dependency injection pattern further promotes the testability of the application, making it easy to create unit tests that exercise view models.
Next Topic | Previous Topic | Home
Last built: February 10, 2012