Model-View-ViewModel (Mvvm) is a fantastic pattern when designing cross platform mobile applications. With Mvvm, we can write our ViewModels in shared code and target Xamarin.iOS, Xamarin.Android, Windows Phone, and Windows Store apps. Libraries such as Xamarin.Forms, MvvmCross, and ReactiveUI provide excellent databinding and commanding functionality.
One thing that bothers me with Mvvm is the amount of boilerplate code that is required to implement the INotifyPropertyChanged interface, which powers our databinding engine.
INotifyPropertyChanged Implementation
using System.Runtime.CompilerServices;
using System.ComponentModel;
public class UserViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _firstName;
public string FirstName {
get {
return _firstName;
}
set {
if (value != _firstName) {
_firstName = value;
NotifyPropertyChanged ();
}
}
}
private string _lastName;
public string LastName {
get {
return _lastName;
}
set {
if (value != _lastName) {
_lastName = value;
NotifyPropertyChanged ();
}
}
}
private void NotifyPropertyChanged ([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null) {
PropertyChanged (this, new PropertyChangedEventArgs (propertyName));
}
}
}
That's a lot of boilerplate cluttering up our code, making it harder to read and understand what this class is really trying to accomplish. The code required by the notification/databinding engine isn't adding any value to our view model and it isn't related to our application or our business logic. Ideally, we'd like to remove that boilerplate code and focus on our app.
Clean ViewModel with Auto Properties
public class UserViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
Luckily, there is an amazing library named Fody.PropertyChanged that we can leverage that will allow us to write only the code that we need, but rewrite the resulting assembly to implement INotifyPropertyChanged for us. All we need to do is add the PropertyChanged.Fody Nuget Package to our project, and add an attribute to our ViewModel.
ViewModel + Fody.PropertyChanged
using PropertyChanged;
[ImplementPropertyChanged]
public class UserViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
Out of the box, this all works just fine using Visual Studio on Windows. There are a few problems currently with Xamarin Studio though. Adding the Nuget package is successful, but the project fails to compile. Fixing these issues is easy though.
Add the PropertyChanged.Fody package via Nuget
Install-Package PropertyChanged.Fody
The Nuget package adds a FodyWeavers.xml file to the project, but leaves it empty
- Add <PropertyChanged /> to FodyWeavers.xml
<?xml version="1.0" encoding="utf-8" ?>
<Weavers>
<PropertyChanged />
</Weavers>
- Set FodyWeavers.xml to Build Action -> Content
- Set FodyWeavers.xml to Quick Properties -> Copy to Output Directory
- Build
Viewing the resulting assembly in Xamarin Studio's Show Disassembly window gives the follwing result
Disassembled Code with Fody
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace TestFody
{
public class UserViewModelWithFody : INotifyPropertyChanged
{
//
// Properties
//
public string FirstName {
[CompilerGenerated]
get {
return this.<FirstName>k__BackingField;
}
[CompilerGenerated]
set {
if (string.Equals (this.<FirstName>k__BackingField, value, StringComparison.Ordinal)) {
return;
}
this.<FirstName>k__BackingField = value;
this.OnPropertyChanged ("FirstName");
}
}
public string LastName {
[CompilerGenerated]
get {
return this.<LastName>k__BackingField;
}
[CompilerGenerated]
set {
if (string.Equals (this.<LastName>k__BackingField, value, StringComparison.Ordinal)) {
return;
}
this.<LastName>k__BackingField = value;
this.OnPropertyChanged ("LastName");
}
}
//
// Methods
//
public virtual void OnPropertyChanged (string propertyName)
{
PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
if (propertyChanged != null) {
propertyChanged (this, new PropertyChangedEventArgs (propertyName));
}
}
//
// Events
//
[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
}
}