The Compositional Approach to Model View ViewModel with the Observable Property
Refactor your code by using the ObservableProperty to create change events and compose with other properties.
Join the DZone community and get the full member experience.
Join For FreeMVVM and XAML play really well together. Main building block for building UIs in XAML is the ability to bind with view models (VM in MVVM, we are talking about half of the pattern here). One way or two way binding, it doesn’t matter. Your logic can update properties and UIs will magically refresh.
Bindable object and its properties
XAML expects view models to satisfy certain criteria to achieve that binding. Classes which implement INotifyPropertyChanged
are considered bindable and any change to the underlying property must use notifications to correctly notify XAML that value needs to be fetched again.
Consider this small view model:
public class UserViewModel : INotifyPropertyChanged
{
// Implementing INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
// Cache across instances
private static readonly PropertyChangedEventArgs NameChangedArgs
= new PropertyChangedEventArgs(nameof(Name));
private string _name;
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
PropertyChanged?.Invoke(this, NameChangedArgs);
}
}
}
}
There is quite a lot of noise in there and we have just implemented one property (correctly). The code looks repetitive and lots of MVVM frameworks sprung out with helper classes for reducing the noise. One such example is MVVMLight, an awesome library for implementing MVVM quickly. Our class from above can be shortened into:
public class UserViewModel : ViewModelBase // implements INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set { Set(ref _name, value); }
}
}
We are still left with the implementation details, namely our private field and we have lost some small amount of performance, but the reduction per property and class is obvious.
One can reduce this code even further by removing the need for the backing field. You can find such attempt inYAWL.Common.Mvvm.ViewModelBase:
public class UserViewModel : YAWL.Common.MVVM.ViewModelBase
{
public string Name
{
get { return Get<string>(); }
set { Set(value); }
}
}
Private field is automatically generated for each property and put inside a dictionary for later lookup. Further reduction in code requires small sacrifice in the performance department.
However, one can go even further and use Fody, PostSharp and any other weaver to generate notification code as a part of build process. We are left with the simple and obvious class (using Fody/PropertyChanged):
[ImplementPropertyChanged]
public class UserViewModel
{
public string Name { get; set; }
}
Short, no performance penalty, small build time increase and perhaps a lack of clarity if one is unfamiliar with the weaving framework.
Derived properties
Regardless of the method chosen for implementing view models, there is one small problem: creating properties that depend on other properties. For example, one could implement shopping car view model like this:
public class CartViewModel : ViewModelBase
{
public ObservableCollection<ItemViewModel> Items { get; }
public double Price => Items.Sum(x => x.Price);
}
It is quite clear that Price
is nothing more than a sum of prices for items in the Items
property. Since it has no setter, it won’t update itself whenever an item is added or removed from the cart. We can still manually trigger the update by adding an event handler on the Items
collection which will notify whenever it changes. The code might look something like this:
public CartViewModel()
{
Items = new ObservableCollection<ItemViewModel>();
Items.CollectionChanged += (s, e) => OnPropertyChanged(nameof(Price));
}
There are a couple of ways one could write such dependencies and there are libraries out there that help with building such notification chains. Notifying other properties on changes quickly becomes tedious and bits of notifications are scattered all over view models. Even worse, chaining notifications across parent/child relationships become cumbersome as everything needs to be done manually. And when code evolves, these relationships are so coupled that they increase maintenance cost.
It is quite clear that dependent and derived properties cannot be created from other properties. You cannot pass a property to someone else and allow them to depend on changes. Just having a single level of notifications for binding XAML to view models is rather limiting as apps grow in complexity.
Wouldn’t something as easy as c := a + b
be better?
There is a way to do that.
ObservableProperty
Inspired by ReactiveProperty and ReactiveUI we can build a property that is capable of emitting change events and can compose with other properties. Let’s take a look at how we would refactor our code from above:
public class UserViewModel
{
public ObservableProperty<string> Name { get; } = new ObservableProperty<string>();
}
Slightly verbose, no base class required and XAML needs to change from {Binding Name}
to {Binding Name.Value}
since the value is now wrapped similar to the Nullable<T>
class. To change the value, change the inner Value
property.
Name.Value = "new name";
Ok, so is the increase of complexity when accessing properties worth it? Some view models might not benefit from this approach, some others might. However, applying this pattern throughout the codebase yields consistency.
Let’s take a look at how we would write derived properties. Readers familiar with LINQ will notice similarity with the observable properties.
public class CartViewModel : ViewModelBase
{
public ObservableCollection<ItemViewModel> Items { get; }
public ObservableProperty<double> Price { get; }
public CartViewModel()
{
Items = new ObservableCollection<ItemViewModel>();
Price = Items.Reduce(enumerable => enumerable.Sum(x => x.Price));
}
}
Reduce is an extension method that will attach itself to an ObservableCollection
and monitor any changes. Give a reducer lambda of type Func<IEnumerable<T>, R>
the initial value is calculated. When the original collection changes, recalculation will be performed and the target property will update itself.
The code is concise, consistent and written in a single place declaratively. By bringing derived properties initialization in a single place, one no longer has to scan through the entire file or search for references if they want to understand when it changes. It also prevents forgetting to update a dependent/derived property when value changes. Less work for programmer means better code. This code is also reactive which is important when we want to decouple things.
Let’s take a look at other composition operators.
FirstName = new ObservableProperty<string>();
LastName = new ObservableProperty<string>();
Initial = FirstName.Map(s => s?.Length > 0 ? s[0] : '')
FullName = FirstName.Combine(LastName, (first, last) => $"{first} {last}");
CanRegister = IsUsernameAvailable && InternetHelper.HasInternetConnection
Notice that the last example shows how to combine boolean properties naturally and concisely.
Map
creates a new dependent property that given an transformation function ensures that the target property is always a transformed version of the original value.Combine
enables building new values by combining several values with the specified combiner function.
Readers familiar with Rx will notice that these are the same foundational blocks used on streams. Similar implementations can be found in the already mentioned ReactiveUI library. This style is actually inspired by functional programming and tries to build complex features with common operations and simple building blocks.
In next posts we will examine how to transform other building blocks in MVVM like commands.
ObservableProperty
can be found over at github: YAWL.Composition.ObservableProperty
.
Published at DZone with permission of Toni Petrina, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments