Mono for Android

Building Android Apps with MVVM and Data Binding

The Model-View-ViewModel (MVVM) pattern is for more than just Microsoft .NET Framework projects. Find out how to apply it to Android development through its open source counterpart, MvvmCross.

If you've been working with Microsoft technologies in recent years you might already be familiar with the Model-View-ViewModel (MVVM) pattern. Based on the presentation model and Model View Controller (MVC) design patterns, Microsoft introduced MVVM as the preferred pattern in many of its platforms, including Silverlight, Windows Presentation Foundation (WPF) and Windows Phone. Even though it's the pattern of choice in Microsoft frameworks, third-party frameworks make it possible to extend MVVM into other platforms, such as iOS and Android, as well. In this article I'll take a look at the basics of MVVM along with its benefits, and then dig into how to apply MVVM to Android applications using the MvvmCross framework.

MVVM Primer
As mentioned, MVVM is a variant of the MVC pattern that's common in many modern Web frameworks, among other places. The model and view aspects are essentially the same between the two patterns, but view models and controllers are where the key differences are found. Let's take a look at the three main parts of the MVVM pattern:

Model -- Just like in MVC, the model represents the domain model and core business logic.

View -- Also like MVC, the view represents the interface with which the user interacts.

View Model -- The view model is a mediator between the model and view. It's responsible for exposing data to the view for it to display, as well as providing commands the view can use to communicate events back down to the view model.

Data binding also plays a large role in MVVM applications, allowing the view to declaratively bind itself to the view model data and commands. This is a powerful concept, which you'll see more of in the examples later on. The view model is also responsible for publishing events when its data has been updated so that the view can stay up-to-date.

Now that you're familiar with the basic building blocks of the MVVM pattern, you might be wondering what its benefits are. The first big effect of applying the pattern is that you end up with a clean separation of application behavior from application presentation, which leads you to write much cleaner and more maintainable code. Because behavior and state are isolated from the view's representation of them, the view models also become both testable and reusable across multiple platforms.

This separation can save you a lot of time and effort when targeting multiple platforms. The views should vary based on the platform, but the models and general behavior often remain consistent across platforms. You can even write unit tests that can test a large percentage of your application without having to fire up the actual app and click through. These tests can even be shared and reused across platforms, allowing you to make sure your view models work properly everywhere they need to.

Enter MvvmCross
As the name implies, MvvmCross is an open source, cross-platform MVVM framework for Windows Phone, Mono for Android, MonoTouch, the Windows Runtime and more. Part of bringing MVVM to platforms where it wasn't previously available means that it also enables you to take advantage of rich data binding support in your views. This feature is quite powerful, and can save you a lot of time and code.

As discussed in the last section, view models will be agnostic to any particular platform, therefore they can be shared across all of these different platforms. In most cases the shared models and view models will contain a majority of your application's code, leaving each platform-specific UI layer thin and native. You can fully take advantage of what each platform has to offer and provide the user with a first-class experience without having to rewrite the bulk of your app every time.

Internally, MvvmCross takes advantage of the Portable Class Library (PCL) for its implementation in order to enable easy code sharing across platforms. It does not, however, impose any PCL requirements on your application. You're free to use whichever cross-platform techniques you wish in your app, such as file linking and conditional compilation. MvvmCross also exposes plug-in architecture for adding new features with both shared code and platform-specific implementation pieces. Some examples of plug-ins that ship with MvvmCross are JavaScript Object Notation (JSON) serialization, e-mail composition and opening a page in a Web browser. You can also provide your own plug-ins for any other behavior you want to add into your apps.

One other important aspect of the MvvmCross framework is that it's extensible and customizable, similar to what you'd find in ASP.NET MVC, if you've worked with that before. If there's a certain piece of the pipeline you want to customize for your application, odds are that you can swap that piece out with your own custom implementation and get the behavior you want.

Another big advantage of the framework is that it allows for the use of dependency injection in your models and view models. Dependency injection is something that's normally quite difficult to do in Android applications because there are no good injection hooks provided by Android (you'd typically have to use the service location pattern). In MvvmCross you can actually achieve proper dependency injection if you want to take that approach.

These are just a few of the highlights of the framework, but they should give you an idea of what you get from using it. Instead of boring you with more feature lists, let's jump into some code and see what it's like to use MvvmCross to build an Android application.

Let's Code!
In order to demonstrate using the framework, I'll build a simple app that provides a search form to allow the user to search for different types of beer in a database. There are a few different pieces involved in setting up an MvvmCross application that I won't cover here, but you can download the project to get the full working application. Instead, I'll focus on the more interesting parts of MVVM.

First things first: you'll need a model. For the sake of simplicity, just create a Beer class that stores its name and brewery:

public class Beer
{
  public string Name { get; set; }
  public string BreweryName { get; set; }
} 

Next, you'll need to create a couple of view models. In this app there will be two screens. The first screen has the search form and displays search results, while the second screen shows details about a beer selected from those results. Start out by creating a new class named SearchViewModel, as shown in Listing 1.

Listing 1. The SearchViewModel class.
public class SearchViewModel : MvxViewModel
{
  private readonly IList<Beer> _allBeers = new List<Beer>
    {
      new Beer { BreweryName = "Dogfish Head", Name = "60 Minute IPA" },
      new Beer { BreweryName = "Kelso", Name = "Nut Brown Lager" },
      new Beer { BreweryName = "Sierra Nevada", Name = "Torpedo Extra IPA" },
      new Beer { BreweryName = "Sixpoint", Name = "Gorilla Warfare Coffee Porter" },
      new Beer { BreweryName = "Sixpoint", Name = "Sweet Action" }
    };

  private IList<Beer> _beers;
  public IList<Beer> Beers
  {
    get { return _beers; }
    set
    {
      _beers = value;
      RaisePropertyChanged(() => Beers);
    }
  }

 private string _query;
 public string Query
 {
   get { return _query; }
   set 
   { 
     _query = value;
     RaisePropertyChanged(() => Query);
   }
 }

 public ICommand SearchCommand
 {
   get { return new MvxRelayCommand(search); }
 }

 public ICommand SelectBeerCommand
 {
   get { return new MvxRelayCommand<Beer>(selectBeer); }
 }

 private void search()
 {
   if (string.IsNullOrWhiteSpace(Query))
   {
     Beers = _allBeers;
   }
   else
   {
     Beers =
       _allBeers
         .Where(beer => beer.Name.ToLowerInvariant().Contains(Query.ToLowerInvariant()))
         .ToList();
   }
 }

 private void selectBeer(Beer beer)
 {
   RequestNavigate<BeerViewModel>(
     new Dictionary<string, string>
       {
         {"name", beer.Name}, 
         {"breweryName", beer.BreweryName}
       }
     );
 } 
}

This might look like a lot of code but it's really very simple, so let's break it down. First, you'll notice that it uses a hardcoded list of beers to search from, just for the sake of a simple demo. Next, take a look at the public properties Beers and Query. These provide data endpoints for the view to use, but the setters might look a bit strange if you haven't used the MVVM pattern before. Each setter makes a call to the RaisePropertyChanged method, which will send a signal up to the view, letting it know that this property has changed. This allows the view to stay in sync with the view model state.

Finally, there's a pair of commands, SearchCommand and SelectBeerCommand. As the names imply, these allow the view to send an event back down to the view model for processing. When SearchCommand is triggered it executes a search based on the value of Query, which is set by the view, as you'll see later on. Once it has its results it sets the value of the Beers property. An important thing to note here is that it uses the property to set the value instead of going directly to the private variable that it uses. This is important so that you make sure RaisePropertyChanged is called, and the view is notified of the change.

When SelectBeerCommand is fired it takes the beer passed to it as an argument and requests to navigate to the next view model. When navigating to a new view model, MvvmCross searches for a view in your application that has registered that it handles the requested view model type. The arguments provided here will be passed in as arguments to the requested view model constructor.

In this case there's a 1:1 relationship between view models and screens in the application, but this isn't required. You can actually control the presentation of your views and view models depending on your needs, so it's possible to have more than one visible at a time. For example, in the phone version of your app you might keep the 1:1 ratio due to the smaller screen size, but in the tablet version you can present more than one view at once to take advantage of the larger screen.

That's all you need in this view model, so now let's look at what the view looks like. The activity code is very simple, as all of your logic will be handled by the view model and binding:

[Activity(Label = "Beer Searcher")]
public class SearchView : MvxBindingActivityView<SearchViewModel>
{
  protected override void OnViewModelSet()
  {
    SetContentView(Resource.Layout.Search);
  }
}

Keeping these view classes as lightweight as possible will go a long way toward helping make your app's logic more reusable and testable. The important thing to note here is that the class inherits from MvxBindingActivityView<SearchViewModel>, which is how MvvmCross knows to load this view when it needs to navigate to SearchViewModel. The view then loads its content from Search.axml, as shown in Listing 2.

Listing 2. View loads its content from Search.axml.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:local="http://schemas.android.com/apk/res/com.gregshackles.beersearcher"
  android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
  <EditText
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    local:MvxBind="{'Text':{'Path':'Query','Mode':'TwoWay'}}" />
  <Button
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="Find Beers"
    local:MvxBind="{'Click': {'Path': 'SearchCommand'}}" />

  <Mvx.MvxBindableListView
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    local:MvxBind="{'ItemsSource':{'Path':'Beers'},
      'ItemClick':{'Path':'SelectBeerCommand'}}"
    local:MvxItemTemplate="@layout/searchresult" />
</LinearLayout>

As you can see, Listing 2 looks much like a normal Android layout file, with some minor-looking but powerful upgrades. Through the local:MvxBind attributes we can take advantage of data binding to display and update the view model. Data binding in MvvmCross is written using JSON syntax, so it might be a bit different than you're used to on platforms such as Windows Phone, but it's pretty simple to get used to. Setting the Mode to TwoWay in the text box tells the binding that updates to the text box in the UI should be passed down to the view model. If this mode hadn't been set, the view model would be able to send changes to the view, but not the other way around.

You can also see that there's a specialized list view here, which comes as part of the MvvmCross package and provides data binding support in lists. You simply provide the list of beers as the binding source, the command to execute when an item is selected and the item template to use when populating a list item -- and then you're good to go!

The next thing you need is the view model for the second screen of the app that shows details about the beer. Let's create a new class called BeerViewModel, as shown in Listing 3.

Listing 3. The BeerViewModel class.
public class BeerViewModel : MvxViewModel
{
  private readonly string _name;
  private readonly string _breweryName;

  public BeerViewModel(string name, string breweryName)
  {
    _name = name;
    _breweryName = breweryName;
  }

  public string Name { get { return _name; } }
  public string BreweryName { get { return _breweryName; } }
} 

This is a simple view model that takes the name and brewery in the constructor and exposes them via properties. Note that the constructor's parameter names match up to the ones used in the call to RequestNavigate earlier. Out of the box you can use primitive types and strings as parameters. This makes sure that MvvmCross is able to properly restore your app in cases where the user switches away and the OS stops your app from running. As with most things in MvvmCross, you can also customize this behavior if you need to. The rest of the wiring for this view is very similar to the last example, so I'll skip that here, but you can refer to the full project to see how it's all set up. An example of what the app looks like when running can be seen in Figure 1.

[Click on image for larger view.] Figure 1. An example of the running app.

This example only scratches the surface of what you can do with MvvmCross in your applications, but I hope it helps you get started in checking it out. The big thing to keep in mind here is that the model and view models created in this example are completely portable, and can also easily be used for iOS, Windows Phone, the Windows Runtime and more. If you're interested in diving further into MvvmCross, I suggest starting with the following resources:

About the Author

Greg Shackles, Microsoft MVP, Xamarin MVP, is a Principal Engineer at Olo. He hosts the Gone Mobile podcast, organizes the NYC Mobile .NET Developers Group, and wrote Mobile Development with C# (O'Reilly). Greg is obsessed with heavy metal, baseball, and craft beer (he’s an aspiring home brewer). Contact him at Twitter @gshackles.

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube