33 min read

In this article, by Mark Reynolds, author of the book Xamarin Essentials, we will take the next step and look at how the use of design patterns and frameworks can increase the amount of code that can be reused. We will cover the following topics:

  • An introduction to MvvmCross
  • The MVVM design pattern
  • Core concepts
  • Views, ViewModels, and commands
  • Data binding
  • Navigation (ViewModel to ViewModel)
  • The project organization
  • The startup process
  • Creating NationalParks.MvvmCross

Our approach will be to introduce the core concepts at a high level and then dive in and create the national parks sample app using MvvmCross. This will give you a basic understanding of how to use the framework and the value associated with its use. With that in mind, let’s get started.

(For more resources related to this topic, see here.)

Introducing MvvmCross

MvvmCross is an open source framework that was created by Stuart Lodge. It is based on the Model-View-ViewModel (MVVM) design pattern and is designed to enhance code reuse across numerous platforms, including Xamarin.Android, Xamarin.iOS, Windows Phone, Windows Store, WPF, and Mac OS X. The MvvmCross project is hosted on GitHub and can be accessed at https://github.com/MvvmCross/MvvmCross.

The MVVM pattern

MVVM is a variation of the Model-View-Controller pattern. It separates logic traditionally placed in a View object into two distinct objects, one called View and the other called ViewModel. The View is responsible for providing the user interface and the ViewModel is responsible for the presentation logic. The presentation logic includes transforming data from the Model into a form that is suitable for the user interface to work with and mapping user interaction with the View into requests sent back to the Model. The following diagram depicts how the various objects in MVVM communicate:

Xamarin Essentials

While MVVM presents a more complex implementation model, there are significant benefits of it, which are as follows:

  • ViewModels and their interactions with Models can generally be tested using frameworks (such as NUnit) that are much easier than applications that combine the user interface and presentation layers
  • ViewModels can generally be reused across different user interface technologies and platforms

These factors make the MVVM approach both flexible and powerful.

Views

Views in an MvvmCross app are implemented using platform-specific constructs. For iOS apps, Views are generally implemented as ViewControllers and XIB files. MvvmCross provides a set of base classes, such as MvxViewContoller, that iOS ViewControllers inherit from. Storyboards can also be used in conjunction with a custom presenter to create Views; we will briefly discuss this option in the section titled Implementing the iOS user interface later in this article.

For Android apps, Views are generally implemented as MvxActivity or MvxFragment along with their associated layout files.

ViewModels

ViewModels are classes that provide data and presentation logic to views in an app. Data is exposed to a View as properties on a ViewModel, and logic that can be invoked from a View is exposed as commands. ViewModels inherit from the MvxViewModel base class.

Commands

Commands are used in ViewModels to expose logic that can be invoked from the View in response to user interactions. The command architecture is based on the ICommand interface used in a number of Microsoft frameworks such as Windows Presentation Foundation (WPF) and Silverlight. MvvmCross provides IMvxCommand, which is an extension of ICommand, along with an implementation named MvxCommand.

The commands are generally defined as properties on a ViewModel. For example:

public IMvxCommand ParkSelected { get; protected set; }

Each command has an action method defined, which implements the logic to be invoked:

protected void ParkSelectedExec(NationalPark park)
{
   . . .// logic goes here
}

The commands must be initialized and the corresponding action method should be assigned:

ParkSelected =
   new MvxCommand<NationalPark> (ParkSelectedExec);

Data binding

Data binding facilitates communication between the View and the ViewModel by establishing a two-way link that allows data to be exchanged. The data binding capabilities provided by MvvmCross are based on capabilities found in a number of Microsoft XAML-based UI frameworks such as WPF and Silverlight. The basic idea is that you would like to bind a property in a UI control, such as the Text property of an EditText control in an Android app to a property of a data object such as the Description property of NationalPark. The following diagram depicts this scenario:

Xamarin Essentials

The binding modes

There are four different binding modes that can be used for data binding:

  • OneWay binding: This mode tells the data binding framework to transfer values from the ViewModel to the View and transfer any updates to properties on the ViewModel to their bound View property.
  • OneWayToSource binding: This mode tells the data binding framework to transfer values from the View to the ViewModel and transfer updates to View properties to their bound ViewModel property.
  • TwoWay binding: This mode tells the data binding framework to transfer values in both directions between the ViewModel and View, and updates on either object will cause the other to be updated. This binding mode is useful when values are being edited.
  • OneTime binding: This mode tells the data binding framework to transfer values from ViewModel to View when the binding is established; in this mode, updates to ViewModel properties are not monitored by the View.

The INotifyPropertyChanged interface

The INotifyPropertyChanged interface is an integral part of making data binding work effectively; it acts as a contract between the source object and the target object. As the name implies, it defines a contract that allows the source object to notify the target object when data has changed, thus allowing the target to take any necessary actions such as refreshing its display.

The interface consists of a single event—the PropertyChanged event—that the target object can subscribe to and that is triggered by the source if a property changes. The following sample demonstrates how to implement INotifyPropertyChanged:

public class NationalPark : INotifyPropertyChanged
{
  public event PropertyChangedEventHandler
     PropertyChanged;
// rather than use "… code" it is safer to use
// the comment form
string _name;
public string Name
{
   get { return _name; }
   set
   {
       if (value.Equals (_name,
           StringComparison.Ordinal))
       {
     // Nothing to do - the value hasn't changed;
     return;
       }
       _name = value;
       OnPropertyChanged();
   }
}
. . .
void OnPropertyChanged(
   [CallerMemberName] string propertyName = null)
{
     var handler = PropertyChanged;
if (handler != null)
{
     handler(this,
           new PropertyChangedEventArgs(propertyName));
}
}
}

Binding specifications

Bindings can be specified in a couple of ways. For Android apps, bindings can be specified in layout files. The following example demonstrates how to bind the Text property of a TextView instance to the Description property in a NationalPark instance:

<TextView
   android_layout_width="match_parent"
   android_layout_height="wrap_content"
   android_id="@+id/descrTextView"
   local_MvxBind="Text Park.Description" />

For iOS, binding must be accomplished using the binding API. CreateBinding() is a method than can be found on MvxViewController. The following example demonstrates how to bind the Description property to a UILabel instance:

this.CreateBinding (this.descriptionLabel).
   To ((DetailViewModel vm) => vm.Park.Description).
   Apply ();

Navigating between ViewModels

Navigating between various screens within an app is an important capability. Within a MvvmCross app, this is implemented at the ViewModel level so that navigation logic can be reused. MvvmCross supports navigation between ViewModels through use of the ShowViewModel<T>() method inherited from MvxNavigatingObject, which is the base class for MvxViewModel. The following example demonstrates how to navigate to DetailViewModel:

ShowViewModel<DetailViewModel>();

Passing parameters

In many situations, there is a need to pass information to the destination ViewModel. MvvmCross provides a number of ways to accomplish this. The primary method is to create a class that contains simple public properties and passes an instance of the class into ShowViewModel<T>(). The following example demonstrates how to define and use a parameters class during navigation:

public class DetailParams
{
   public int ParkId { get; set; }
}
 
// using the parameters class
ShowViewModel<DetailViewModel>(
new DetailViewParam() { ParkId = 0 });

To receive and use parameters, the destination ViewModel implements an Init() method that accepts an instance of the parameters class:

public class DetailViewModel : MvxViewModel
{
   . . .
   public void Init(DetailViewParams parameters)
   {
       // use the parameters here . . .
   }
}

Solution/project organization

Each MvvmCross solution will have a single core PCL project that houses the reusable code and a series of platform-specific projects that contain the various apps. The following diagram depicts the general structure:

Xamarin Essentials

The startup process

MvvmCross apps generally follow a standard startup sequence that is initiated by platform-specific code within each app. There are several classes that collaborate to accomplish the startup; some of these classes reside in the core project and some of them reside in the platform-specific projects. The following sections describe the responsibilities of each of the classes involved.

App.cs

The core project has an App class that inherits from MvxApplication. The App class contains an override to the Initialize() method so that at a minimum, it can register the first ViewModel that should be presented when the app starts:

RegisterAppStart<ViewModels.MasterViewModel>();

Setup.cs

Android and iOS projects have a Setup class that is responsible for creating the App object from the core project during the startup. This is accomplished by overriding the CreateApp() method:

protected override IMvxApplication CreateApp()
{
   return new Core.App();
}

For Android apps, Setup inherits from MvxAndroidSetup. For iOS apps, Setup inherits from MvxTouchSetup.

The Android startup

Android apps are kicked off using a special Activity splash screen that calls the Setup class and initiates the MvvmCross startup process. This is all done automatically for you; all you need to do is include the splash screen definition and make sure it is marked as the launch activity. The definition is as follows:

[Activity(
Label="NationalParks.Droid", MainLauncher = true,
Icon="@drawable/icon", Theme="@style/Theme.Splash",
NoHistory=true,
ScreenOrientation = ScreenOrientation.Portrait)]
public class SplashScreen : MvxSplashScreenActivity
{
   public SplashScreen():base(Resource.Layout.SplashScreen)
   {
   }
}

The iOS startup

The iOS app startup is slightly less automated and is initiated from within the FinishedLaunching() method of AppDelegate:

public override bool FinishedLaunching (
   UIApplication app, NSDictionary options)
{
   _window = new UIWindow (UIScreen.MainScreen.Bounds);
 
   var setup = new Setup(this, _window);
   setup.Initialize();
   var startup = Mvx.Resolve<IMvxAppStart>();
   startup.Start();
 
   _window.MakeKeyAndVisible ();
 
   return true;
}

Creating NationalParks.MvvmCross

Now that we have basic knowledge of the MvvmCross framework, let’s put that knowledge to work and convert the NationalParks app to leverage the capabilities we just learned.

Creating the MvvmCross core project

We will start by creating the core project. This project will contain all the code that will be shared between the iOS and Android app primarily in the form of ViewModels. The core project will be built as a Portable Class Library.

To create NationalParks.Core, perform the following steps:

  1. From the main menu, navigate to File | New Solution.
  2. From the New Solution dialog box, navigate to C# | Portable Library, enter NationalParks.Core for the project Name field, enter NationalParks.MvvmCross for the Solution field, and click on OK.
  3. Add the MvvmCross starter package to the project from NuGet. Select the NationalParks.Core project and navigate to Project | Add Packages from the main menu. Enter MvvmCross starter in the search field.
  4. Select the MvvmCross – Hot Tuna Starter Pack entry and click on Add Package.Xamarin Essentials
  5. A number of things were added to NationalParks.Core as a result of adding the package, and they are as follows:
    • A packages.config file, which contains a list of libraries (dlls) associated with the MvvmCross starter kit package. These entries are links to actual libraries in the Packages folder of the overall solution.
    • A ViewModels folder with a sample ViewModel named FirstViewModel.
    • An App class in App.cs, which contains an Initialize() method that starts the MvvmCross app by calling RegisterAppStart() to start FirstViewModel. We will eventually be changing this to start the MasterViewModel class, which will be associated with a View that lists national parks.

Creating the MvvmCross Android app

The next step is to create an Android app project in the same solution.

To create NationalParks.Droid, complete the following steps:

  1. Select the NationalParks.MvvmCross solution, right-click on it, and navigate to Add | New Project.
  2. From the New Project dialog box, navigate to C# | Android | Android Application, enter NationalParks.Droid for the Name field, and click on OK.
  3. Add the MvvmCross starter kit package to the new project by selecting NationalParks.Droid and navigating to Project | Add Packages from the main menu.
  4. A number of things were added to NationalParks.Droid as a result of adding the package, which are as follows:
    • packages.config: This file contains a list of libraries (dlls) associated with the MvvmCross starter kit package. These entries are links to an actual library in the Packages folder of the overall solution, which contains the actual downloaded libraries.
    • FirstView : This class is present in the Views folder, which corresponds to FirstViewModel, which was created in NationalParks.Core.
    • FirstView: This layout is present in Resourceslayout, which is used by the FirstView activity. This is a traditional Android layout file with the exception that it contains binding declarations in the EditView and TextView elements.
    • Setup: This file inherits from MvxAndroidSetup. This class is responsible for creating an instance of the App class from the core project, which in turn displays the first ViewModel via a call to RegisterAppStart().
    • SplashScreen: This class inherits from MvxSplashScreenActivity. The SplashScreen class is marked as the main launcher activity and thus initializes the MvvmCross app with a call to Setup.Initialize().
  5. Add a reference to NationalParks.Core by selecting the References folder, right-click on it, select Edit References, select the Projects tab, check NationalParks.Core, and click on OK.
  6. Remove MainActivity.cs as it is no longer needed and will create a build error. This is because it is marked as the main launch and so is the new SplashScreen class. Also, remove the corresponding Resourceslayoutmain.axml layout file.
  7. Run the app. The app will present FirstViewModel, which is linked to the corresponding FirstView instance with an EditView class, and TextView presents the same Hello MvvmCross text. As you edit the text in the EditView class, the TextView class is automatically updated by means of data binding. The following screenshot depicts what you should see:Xamarin Essentials

Reusing NationalParks.PortableData and NationalParks.IO

Before we start creating the Views and ViewModels for our app, we first need to bring in some code from our previous efforts that can be used to maintain parks. For this, we will simply reuse the NationalParksData singleton and the FileHandler classes that were created previously.

To reuse the NationalParksData singleton and FileHandler classes, complete the following steps:

  1. Copy NationalParks.PortableData and NationalParks.IO from the solution created in Chapter 6, The Sharing Game in the book Xamarin Essentials (available at https://www.packtpub.com/application-development/xamarin-essentials), to the NationalParks.MvvmCross solution folder.
  2. Add a reference to NationalParks.PortableData in the NationalParks.Droid project.
  3. Create a folder named NationalParks.IO in the NationalParks.Droid project and add a link to FileHandler.cs from the NationalParks.IO project. Recall that the FileHandler class cannot be contained in the Portable Class Library because it uses file IO APIs that cannot be references from a Portable Class Library.
  4. Compile the project. The project should compile cleanly now.

Implementing the INotifyPropertyChanged interface

We will be using data binding to bind UI controls to the NationalPark object and thus, we need to implement the INotifyPropertyChanged interface. This ensures that changes made to properties of a park are reported to the appropriate UI controls.

To implement INotifyPropertyChanged, complete the following steps:

  1. Open NationalPark.cs in the NationalParks.PortableData project.
  2. Specify that the NationalPark class implements INotifyPropertyChanged interface.
  3. Select the INotifyPropertyChanged interface, right-click on it, navigate to Refactor | Implement interface, and press Enter. Enter the following code snippet:
    public class NationalPark : INotifyPropertyChanged
    {
       public event PropertyChangedEventHandler
           PropertyChanged;
       . . .
    }
  4. Add an OnPropertyChanged() method that can be called from each property setter method:
    void OnPropertyChanged(
       [CallerMemberName] string propertyName = null)
    {
       var handler = PropertyChanged;
       if (handler != null)
       {
           handler(this,
               new PropertyChangedEventArgs(propertyName));
       }
    }
  5. Update each property definition to call the setter in the same way as it is depicted for the Name property:
    string _name;
    public string Name
    {
    get { return _name; }
    set
    {
       if (value.Equals (_name, StringComparison.Ordinal))
       {
         // Nothing to do - the value hasn't changed;
    return;
       }
       _name = value;
       OnPropertyChanged();
    }
    }
  6. Compile the project. The project should compile cleanly. We are now ready to use the NationalParksData singleton in our new project, and it supports data binding.

Implementing the Android user interface

Now, we are ready to create the Views and ViewModels required for our app. The app we are creating will follow the following flow:

  • A master list view to view national parks
  • A detail view to view details of a specific park
  • An edit view to edit a new or previously existing park

The process for creating views and ViewModels in an Android app generally consists of three different steps:

  1. Create a ViewModel in the core project with the data and event handlers (commands) required to support the View.
  2. Create an Android layout with visual elements and data binding specifications.
  3. Create an Android activity, which corresponds to the ViewModel and displays the layout.

In our case, this process will be slightly different because we will reuse some of our previous work, specifically, the layout files and the menu definitions.

To reuse layout files and menu definitions, perform the following steps:

  1. Copy Master.axml, Detail.axml, and Edit.axml from the Resourceslayout folder of the solution created in Chapter 5, Developing Your First Android App with Xamarin.Android in the book Xamarin Essentials (available at https://www.packtpub.com/application-development/xamarin-essentials), to the Resourceslayout folder in the NationalParks.Droid project, and add them to the project by selecting the layout folder and navigating to Add | Add Files.
  2. Copy MasterMenu.xml, DetailMenu.xml, and EditMenu.xml from the Resourcesmenu folder of the solution created in Chapter 5, Developing Your First Android App with Xamarin.Android in the book Xamarin Essentials (available at https://www.packtpub.com/application-development/xamarin-essentials), to the Resourcesmenu folder in the NationalParks.Droid project, and add them to the project by selecting the menu folder and navigating to Add | Add Files.

Implementing the master list view

We are now ready to implement the first of our View/ViewModel combinations, which is the master list view.

Creating MasterViewModel

The first step is to create a ViewModel and add a property that will provide data to the list view that displays national parks along with some initialization code.

To create MasterViewModel, complete the following steps:

  1. Select the ViewModels folder in NationalParks.Core, right-click on it, and navigate to Add | New File.
  2. In the New File dialog box, navigate to General | Empty Class, enter MasterViewModel for the Name field, and click on New.
  3. Modify the class definition so that MasterViewModel inherits from MvxViewModel; you will also need to add a few using directives:
    . . .
    using Cirrious.CrossCore.Platform;
    using Cirrious.MvvmCross.ViewModels;
    . . .
    namespace NationalParks.Core.ViewModels
    {
    public class MasterViewModel : MvxViewModel
    {
             . . .
       }
    }
  4. Add a property that is a list of NationalPark elements to MasterViewModel. This property will later be data-bound to a list view:
    private List<NationalPark> _parks;
    public List<NationalPark> Parks
    {
       get { return _parks; }
       set { _parks = value;
             RaisePropertyChanged(() => Parks);
       }
    }
  5. Override the Start() method on MasterViewModel to load the _parks collection with data from the NationalParksData singleton. You will need to add a using directive for the NationalParks.PortableData namespace again:
    . . .
    using NationalParks.PortableData;
    . . .
    public async override void Start ()
    {
       base.Start ();
       await NationalParksData.Instance.Load ();
       Parks = new List<NationalPark> (
           NationalParksData.Instance.Parks);
    }
  6. We now need to modify the app startup sequence so that MasterViewModel is the first ViewModel that’s started. Open App.cs in NationalParks.Core and change the call to RegisterAppStart() to reference MasterViewModel:RegisterAppStart<ViewModels.MasterViewModel>();

Updating the Master.axml layout

Update Master.axml so that it can leverage the data binding capabilities provided by MvvmCross.

To update Master.axml, complete the following steps:

  1. Open Master.axml and add a namespace definition to the top of the XML to include the NationalParks.Droid namespace:
    
    

    This namespace definition is required in order to allow Android to resolve the MvvmCross-specific elements that will be specified.

  2. Change the ListView element to a Mvx.MvxListView element:
    <Mvx.MvxListView
       android_layout_width="match_parent"
       android_layout_height="match_parent"
       android_id="@+id/parksListView" />
  3. Add a data binding specification to the MvxListView element, binding the ItemsSource property of the list view to the Parks property of MasterViewModel, as follows:
       . . .
       android_id="@+id/parksListView"
       local_MvxBind="ItemsSource Parks" />
  4. Add a list item template attribute to the element definition. This layout controls the content of each item that will be displayed in the list view:
    local:MvxItemTemplate="@layout/nationalparkitem"
  5. Create the NationalParkItem layout and provide TextView elements to display both the name and description of a park, as follows:
    <LinearLayout 
    
       android_orientation="vertical"
       android_layout_width="fill_parent"
       android_layout_height="wrap_content">
       <TextView
           android_layout_width="match_parent"
           android_layout_height="wrap_content"
            android:textSize="40sp"/>
       <TextView
           android_layout_width="match_parent"
           android_layout_height="wrap_content"
           android_textSize="20sp"/>
    </LinearLayout>
  6. Add data binding specifications to each of the TextView elements:
    . . .
           local_MvxBind="Text Name" />
    . . .
           local_MvxBind="Text Description" />
    . . .

Note that in this case, the context for data binding is an instance of an item in the collection that was bound to MvxListView, for this example, an instance of NationalPark.

Creating the MasterView activity

Next, create MasterView, which is an MvxActivity instance that corresponds with MasterViewModel.

To create MasterView, complete the following steps:

  1. Select the ViewModels folder in NationalParks.Core, right-click on it, navigate to Add | New File.
  2. In the New File dialog, navigate to Android | Activity, enter MasterView in the Name field, and select New.
  3. Modify the class specification so that it inherits from MvxActivity; you will also need to add a few using directives as follows:
    using Cirrious.MvvmCross.Droid.Views;
    using NationalParks.Core.ViewModels;
    . . .
    namespace NationalParks.Droid.Views
    {
       [Activity(Label = "Parks")]
       public class MasterView : MvxActivity
       {
           . . .
       }
    }
  4. Open Setup.cs and add code to initialize the file handler and path for the NationalParksData singleton to the CreateApp() method, as follows:
    protected override IMvxApplication CreateApp()
    {
       NationalParksData.Instance.FileHandler =
           new FileHandler ();
       NationalParksData.Instance.DataDir =
           System.Environment.GetFolderPath(
             System.Environment.SpecialFolder.MyDocuments);
       return new Core.App();
    }
  5. Compile and run the app; you will need to copy the NationalParks.json file to the device or emulator using the Android Device Monitor. All the parks in NationalParks.json should be displayed.

Implementing the detail view

Now that we have the master list view displaying national parks, we can focus on creating the detail view. We will follow the same steps for the detail view as the ones we just completed for the master view.

Creating DetailViewModel

We start creating DetailViewModel by using the following steps:

  1. Following the same procedure as the one that was used to create MasterViewModel, create a new ViewModel named DetailViewModel in the ViewModel folder of NationalParks.Core.
  2. Add a NationalPark property to support data binding for the view controls, as follows:
    protected NationalPark _park;
    public NationalPark Park
    {
       get { return _park; }
       set { _park = value;
             RaisePropertyChanged(() => Park);
         }
    }
  3. Create a Parameters class that can be used to pass a park ID for the park that should be displayed. It’s convenient to create this class within the class definition of the ViewModel that the parameters are for:
    public class DetailViewModel : MvxViewModel
    {
       public class Parameters
       {
           public string ParkId { get; set; }
       }
       . . .
  4. Implement an Init() method that will accept an instance of the Parameters class and get the corresponding national park from NationalParkData:
    public void Init(Parameters parameters)
    {
       Park = NationalParksData.Instance.Parks.
           FirstOrDefault(x => x.Id == parameters.ParkId);
    }

Updating the Detail.axml layout

Next, we will update the layout file. The main changes that need to be made are to add data binding specifications to the layout file.

To update the Detail.axml layout, perform the following steps:

  1. Open Detail.axml and add the project namespace to the XML file:
    
    
  2. Add data binding specifications to each of the TextView elements that correspond to a national park property, as demonstrated for the park name:
    <TextView
       android_layout_width="match_parent"
       android_layout_height="wrap_content"
       android_id="@+id/nameTextView"
       local_MvxBind="Text Park.Name" />

Creating the DetailView activity

Now, create the MvxActivity instance that will work with DetailViewModel.

To create DetailView, perform the following steps:

  1. Following the same procedure as the one that was used to create MasterView, create a new view named DetailView in the Views folder of NationalParks.Droid.
  2. Implement the OnCreateOptionsMenu() and OnOptionsItemSelected() methods so that our menus will be accessible. Copy the implementation of these methods from the solution created in Chapter 6, The Sharing Game in the book Xamarin Essentials (available at https://www.packtpub.com/application-development/xamarin-essentials)[AR4] . Comment out the section in OnOptionsItemSelect() related to the Edit action for now; we will fill that in once the edit view is completed.

Adding navigation

The last step is to add navigation so that when an item is clicked on in MvxListView on MasterView, the park is displayed in the detail view. We will accomplish this using a command property and data binding.

To add navigation, perform the following steps:

  1. Open MasterViewModel and add an IMvxCommand property; this will be used to handle a park that is being selected:

    protected IMvxCommand ParkSelected { get; protected set; }

  2. Create an Action delegate that will be called when the ParkSelected command is executed, as follows:
    protected void ParkSelectedExec(NationalPark park)
    {
       ShowViewModel<DetailViewModel> (
           new DetailViewModel.Parameters ()
               { ParkId = park.Id });
    }
  3. Initialize the command property in the constructor of MasterViewModel:
    ParkClicked =
       new MvxCommand<NationalPark> (ParkSelectedExec);
  4. Now, for the last step, add a data binding specification to MvvListView in Master.axml to bind the ItemClick event to the ParkClicked command on MasterViewModel, which we just created:
    local:MvxBind="ItemsSource Parks; ItemClick ParkClicked"
  5. Compile and run the app. Clicking on a park in the list view should now navigate to the detail view, displaying the selected park.

Implementing the edit view

We are now almost experts at implementing new Views and ViewModels. One last View to go is the edit view.

Creating EditViewModel

Like we did previously, we start with the ViewModel.

To create EditViewModel, complete the following steps:

  1. Following the same process that was previously used in this article to create EditViewModel, add a data binding property and create a Parameters class for navigation.
  2. Implement an Init() method that will accept an instance of the Parameters class and get the corresponding national park from NationalParkData in the case of editing an existing park or create a new instance if the user has chosen the New action. Inspect the parameters passed in to determine what the intent is:
    public void Init(Parameters parameters)
    {
       if (string.IsNullOrEmpty (parameters.ParkId))
           Park = new NationalPark ();
       else
           Park =
               NationalParksData.Instance.
               Parks.FirstOrDefault(
               x => x.Id == parameters.ParkId);
    }

Updating the Edit.axml layout

Update Edit.axml to provide data binding specifications.

To update the Edit.axml layout, you first need to open Edit.axml and add the project namespace to the XML file. Then, add the data binding specifications to each of the EditView elements that correspond to a national park property.

Creating the EditView activity

Create a new MvxActivity instance named EditView to will work with EditViewModel.

To create EditView, perform the following steps:

  1. Following the same procedure as the one that was used to create DetailView, create a new View named EditView in the Views folder of NationalParks.Droid.
  2. Implement the OnCreateOptionsMenu() and OnOptionsItemSelected() methods so that the Done action will accessible from the ActionBar. You can copy the implementation of these methods from the solution created in Chapter 6, The Sharing Game in the book Xamarin Essentials (available at https://www.packtpub.com/application-development/xamarin-essentials). Change the implementation of Done to call the Done command on EditViewModel.

Adding navigation

Add navigation to two places: when New (+) is clicked from MasterView and when Edit is clicked in DetailView. Let’s start with MasterView.

To add navigation from MasterViewModel, complete the following steps:

  1. Open MasterViewModel.cs and add a NewParkClicked command property along with the handler for the command. Be sure to initialize the command in the constructor, as follows:
    protected IMvxCommand NewParkClicked { get; set; }
    protected void NewParkClickedExec()
    {
    ShowViewModel<EditViewModel> ();
    }

    Note that we do not pass in a parameter class into ShowViewModel(). This will cause a default instance to be created and passed in, which means that ParkId will be null. We will use this as a way to determine whether a new park should be created.

  2. Now, it’s time to hook the NewParkClicked command up to the actionNew menu item. We do not have a way to accomplish this using data binding, so we will resort to a more traditional approach—we will use the OnOptionsItemSelected() method. Add logic to invoke the Execute() method on NewParkClicked, as follows:
    case Resource.Id.actionNew:
       ((MasterViewModel)ViewModel).
           NewParkClicked.Execute ();
       return true;

To add navigation from DetailViewModel, complete the following steps:

  1. Open DetailViewModel.cs and add a EditParkClicked command property along with the handler for the command. Be sure to initialize the command in the constructor, as shown in the following code snippet:
    protected IMvxCommand EditPark { get; protected set;}
    protected void EditParkHandler()
    {
       ShowViewModel<EditViewModel> (
           new EditViewModel.Parameters ()
               { ParkId = _park.Id });
    }

    Note that an instance of the Parameters class is created, initialized, and passed into the ShowViewModel() method. This instance will in turn be passed into the Init() method on EditViewModel.

  2. Initialize the command property in the constructor for MasterViewModel, as follows:
    EditPark =
       new MvxCommand<NationalPark> (EditParkHandler);
  3. Now, update the OnOptionsItemSelect() method in DetailView to invoke the DetailView.EditPark command when the Edit action is selected:
    case Resource.Id.actionEdit:
       ((DetailViewModel)ViewModel).EditPark.Execute ();
       return true;
  4. Compile and run NationalParks.Droid. You should now have a fully functional app that has the ability to create new parks and edit the existing parks. Changes made to EditView should automatically be reflected in MasterView and DetailView.

Creating the MvvmCross iOS app

The process of creating the Android app with MvvmCross provides a solid understanding of how the overall architecture works. Creating the iOS solution should be much easier for two reasons: first, we understand how to interact with MvvmCross and second, all the logic we have placed in NationalParks.Core is reusable, so that we just need to create the View portion of the app and the startup code.

To create NationalParks.iOS, complete the following steps:

  1. Select the NationalParks.MvvmCross solution, right-click on it, and navigate to Add | New Project.
  2. From the New Project dialog, navigate to C# | iOS | iPhone | Single View Application, enter NationalParks.iOS in the Name field, and click on OK.
  3. Add the MvvmCross starter kit package to the new project by selecting NationalParks.iOS and navigating to Project | Add Packages from the main menu.
  4. A number of things were added to NationalParks.iOS as a result of adding the package. They are as follows:
    • packages.config: This file contains a list of libraries associated with the MvvmCross starter kit package. These entries are links to an actual library in the Packages folder of the overall solution, which contains the actual downloaded libraries.
    • FirstView: This class is placed in the Views folder, which corresponds to the FirstViewModel instance created in NationalParks.Core.
    • Setup: This class inherits from MvxTouchSetup. This class is responsible for creating an instance of the App class from the core project, which in turn displays the first ViewModel via a call to RegisterAppStart().
    • AppDelegate.cs.txt: This class contains the sample startup code, which should be placed in the actual AppDelete.cs file.

Implementing the iOS user interface

We are now ready to create the user interface for the iOS app. The good news is that we already have all the ViewModels implemented, so we can simply reuse them. The bad news is that we cannot easily reuse the storyboards from our previous work; MvvmCross apps generally use XIB files. One of the reasons for this is that storyboards are intended to provide navigation capabilities and an MvvmCross app delegates that responsibility to ViewModel and presenter. It is possible to use storyboards in combination with a custom presenter, but the remainder of this article will focus on using XIB files, as this is the more common use. The screen layouts can be used as depicted in the following screenshot:

Xamarin Essentials

We are now ready to get started.

Implementing the master view

The first view we will work on is the master view.

To implement the master view, complete the following steps:

  1. Create a new ViewController class named MasterView by right-clicking on the Views folder of NationalParks.iOS and navigating to Add | New File | iOS | iPhone View Controller.
  2. Open MasterView.xib and arrange controls as seen in the screen layouts. Add outlets for each of the edit controls.
  3. Open MasterView.cs and add the following boilerplate logic to deal with constraints on iOS 7, as follows:
    // ios7 layout
    if (RespondsToSelector(new
       Selector("edgesForExtendedLayout")))
       EdgesForExtendedLayout = UIRectEdge.None;
  4. Within the ViewDidLoad() method, add logic to create MvxStandardTableViewSource for parksTableView:
    MvxStandardTableViewSource _source;
    . . .
    _source = new MvxStandardTableViewSource(
       parksTableView,
       UITableViewCellStyle.Subtitle,
       new NSString("cell"),
       "TitleText Name; DetailText Description",
         0);
    parksTableView.Source = _source;

    Note that the example uses the Subtitle cell style and binds the national park name and description to the title and subtitle.

  5. Add the binding logic to the ViewDidShow() method. In the previous step, we provided specifications for properties of UITableViewCell to properties in the binding context. In this step, we need to set the binding context for the Parks property on MasterModelView:
    var set = this.CreateBindingSet<MasterView,
       MasterViewModel>();
    set.Bind (_source).To (vm => vm.Parks);
    set.Apply();
  6. Compile and run the app. All the parks in NationalParks.json should be displayed.

Implementing the detail view

Now, implement the detail view using the following steps:

  1. Create a new ViewController instance named DetailView.
  2. Open DetailView.xib and arrange controls as shown in the following code. Add outlets for each of the edit controls.
  3. Open DetailView.cs and add the binding logic to the ViewDidShow() method:
    this.CreateBinding (this.nameLabel).
       To ((DetailViewModel vm) => vm.Park.Name).Apply ();
    this.CreateBinding (this.descriptionLabel).
       To ((DetailViewModel vm) => vm.Park.Description).
           Apply ();
    this.CreateBinding (this.stateLabel).
       To ((DetailViewModel vm) => vm.Park.State).Apply ();
    this.CreateBinding (this.countryLabel).
       To ((DetailViewModel vm) => vm.Park.Country).
           Apply ();
    this.CreateBinding (this.latLabel).
       To ((DetailViewModel vm) => vm.Park.Latitude).
           Apply ();
    this.CreateBinding (this.lonLabel).
       To ((DetailViewModel vm) => vm.Park.Longitude).
           Apply ();

Adding navigation

Add navigation from the master view so that when a park is selected, the detail view is displayed, showing the park.

To add navigation, complete the following steps:

  1. Open MasterView.cs, create an event handler named ParkSelected, and assign it to the SelectedItemChanged event on MvxStandardTableViewSource, which was created in the ViewDidLoad() method:
    . . .
       _source.SelectedItemChanged += ParkSelected;
    . . .
    protected void ParkSelected(object sender, EventArgs e)
    {
       . . .
    }
  2. Within the event handler, invoke the ParkSelected command on MasterViewModel, passing in the selected park:
    ((MasterViewModel)ViewModel).ParkSelected.Execute (
           (NationalPark)_source.SelectedItem);
  3. Compile and run NationalParks.iOS. Selecting a park in the list view should now navigate you to the detail view, displaying the selected park.

Implementing the edit view

We now need to implement the last of the Views for the iOS app, which is the edit view.

To implement the edit view, complete the following steps:

  1. Create a new ViewController instance named EditView.
  2. Open EditView.xib and arrange controls as in the layout screenshots. Add outlets for each of the edit controls.
  3. Open EditView.cs and add the data binding logic to the ViewDidShow() method. You should use the same approach to data binding as the approach used for the details view.
  4. Add an event handler named DoneClicked, and within the event handler, invoke the Done command on EditViewModel:protected void DoneClicked (object sender, EventArgs e)
    {
       ((EditViewModel)ViewModel).Done.Execute();
    }
  5. In ViewDidLoad(), add UIBarButtonItem to NavigationItem for EditView, and assign the DoneClicked event handler to it, as follows:
    NavigationItem.SetRightBarButtonItem(
       new UIBarButtonItem(UIBarButtonSystemItem.Done,
           DoneClicked), true);

Adding navigation

Add navigation to two places: when New (+) is clicked from the master view and when Edit is clicked on in the detail view. Let’s start with the master view.

To add navigation to the master view, perform the following steps:

  1. Open MasterView.cs and add an event handler named NewParkClicked. In the event handler, invoke the NewParkClicked command on MasterViewModel:
    protected void NewParkClicked(object sender,
           EventArgs e)
    {
       ((MasterViewModel)ViewModel).
               NewParkClicked.Execute ();
    }
  2. In ViewDidLoad(), add UIBarButtonItem to NavigationItem for MasterView and assign the NewParkClicked event handler to it:
    NavigationItem.SetRightBarButtonItem(
       new UIBarButtonItem(UIBarButtonSystemItem.Add,
           NewParkClicked), true);

To add navigation to the details view, perform the following steps:

  1. Open DetailView.cs and add an event handler named EditParkClicked. In the event handler, invoke the EditParkClicked command on DetailViewModel:
    protected void EditParkClicked (object sender,
       EventArgs e)
    {
       ((DetailViewModel)ViewModel).EditPark.Execute ();
    }
  2. In ViewDidLoad(), add UIBarButtonItem to NavigationItem for MasterView, and assign the EditParkClicked event handler to it:
    NavigationItem.SetRightBarButtonItem(
       new UIBarButtonItem(UIBarButtonSystemItem.Edit,
           EditParkClicked), true);

Refreshing the master view list

One last detail that needs to be taken care of is to refresh the UITableView control on MasterView when items have been changed on EditView.

To refresh the master view list, perform the following steps:

  1. Open MasterView.cs and call ReloadData() on parksTableView within the ViewDidAppear() method of MasterView:
    public override void ViewDidAppear (bool animated)
    {
       base.ViewDidAppear (animated);
       parksTableView.ReloadData();
    }
  2. Compile and run NationalParks.iOS. You should now have a fully functional app that has the ability to create new parks and edit existing parks. Changes made to EditView should automatically be reflected in MasterView and DetailVIew.

Considering the pros and cons

After completing our work, we now have the basis to make some fundamental observations. Let’s start with the pros:

  • MvvmCross definitely increases the amount of code that can be reused across platforms. The ViewModels house the data required by the View, the logic required to obtain and transform the data in preparation for viewing, and the logic triggered by user interactions in the form of commands. In our sample app, the ViewModels were somewhat simple; however, the more complex the app, the more reuse will likely be gained.
  • As MvvmCross relies on the use of each platform’s native UI frameworks, each app has a native look and feel and we have a natural layer that implements platform-specific logic when required.
  • The data binding capabilities of MvvmCross also eliminate a great deal of tedious code that would otherwise have to be written.

All of these positives are not necessarily free; let’s look at some cons:

  • The first con is complexity; you have to learn another framework on top of Xamarin, Android, and iOS.
  • In some ways, MvvmCross forces you to align the way your apps work across platforms to achieve the most reuse. As the presentation logic is contained in the ViewModels, the views are coerced into aligning with them. The more your UI deviates across platforms; the less likely it will be that you can actually reuse ViewModels.

With these things in mind, I would definitely consider using MvvmCross for a cross-platform mobile project. Yes, you need to learn an addition framework and yes, you will likely have to align the way some of the apps are laid out, but I think MvvmCross provides enough value and flexibility to make these issues workable. I’m a big fan of reuse and MvvmCross definitely pushes reuse to the next level.

Summary

In this article, we reviewed the high-level concepts of MvvmCross and worked through a practical exercise in order to convert the national parks apps to use the MvvmCross framework and the increase code reuse.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here