The Model-View-ViewModel Pattern aids in decoupling the business and presentation logic of an application from its user interface definition. It encourages re-usable and extensible code that is easier to maintain, test, and evolve.
There are three core components in the MVVM Pattern:
Model:
Encapsulates the data used by the application. It represents the domain model which can be data along with business and validation logic. These can include DTOs (data transfer objects), POCOs (plain old CLR objects), as well as generated entity or proxy objects.
View:
Represents what the user sees on the screen. A View is often defined separately to the code that underpins it. For example, AXML on Android, Storyboards on iOS, or XAML for UWP or Xamarin.Forms apps. A code-first approach can also be used and may be preferred. Both are valid and have their pros and cons. It is recommended that View related code contains only logic that implements visual behavior and does not contain any business logic.
ViewModel:
Defines the functionality and data to be represented by the View and coordinates the View interactions and changes to any Model classes that underpin it. The ViewModel typically exposes Properties and Commands that both informs the state of the View and can be used to notify of state changes or user input.
The relevant supporting components can be found in ../MobCAT/MVVM. Some of these are described in further detail below.
The MobCAT library provides a basic implementation of this pattern through a set of base classes, interfaces, and concrete implementations.
AsyncCommand and Command
Implementations for asynchronous and synchronous bindable commands using the ICommand interface.
The asynchronous variant is typically used in the following way
AsyncCommand _myCommand;
public ICommand MyCommand =>
_myCommand ??
(_myCommand = new AsyncCommand(ExecuteMyCommandAsync, MyCommandCanExecute));
Task ExecuteMyCommandAsync(object arg = null)
=> DoSomethingAsync()
bool MyCommandCanExecute(object arg = null)
=> bool CanDoSomething();
async Task DoSomethingAsync() {}
bool CanDoSomething() => true;
By default, the underlying Task is run each time the Execute or ExecuteAsync is invoked. However, you can change this behavior using the enableCoalescing parameter. When set to true, the ExecuteAsync method will return a reference to the same Task for subsequent invocations until the Task is complete.
AsyncCommand _myCommand = new AsyncCommand(ExecuteMyCommandAsync, MyCommandCanExecute, true)
The Command class is essentially the same but it accepts Action parameters rather than a Task for execute and canExecute.
Command _myCommand;
public ICommand MyCommand =>
_myCommand ??
(_myCommand = new Command(ExecuteMyCommand, MyCommandCanExecute));
void ExecuteMyCommand(object arg = null)
=> DoSomething()
bool MyCommandCanExecute(object arg = null)
=> bool CanDoSomething();
void DoSomething() {}
bool CanDoSomething() => true;
There is a generic variant of the Command class that casts the Execute and CanExecute object parameter to the specified type T.
Command<string> _myCommand = new Command<string>(ExecuteMyCommand, MyCommandCanExecute);
...
void ExecuteMyCommand(string arg = null) {}
bool MyCommandCanExecute(string arg = null) {}
Implementation for INotifyPropertyChanged with support for watching properties. This supports the use of data binding in native Android and iOS projects as well as those leveraging the out-of-the-box binding support provided by Xamarin.Forms. Simplifies updates to backing properties and change notification via the INotifyPropertyChanged PropertyChanged method.
public string MyText
{
get => _myText;
set => RaiseAndUpdate(ref _myText, value);
}
OR
public string MyText
{
get { return _model.Text; }
set { RaiseAndUpdate(() => _model.Text != value, () => _model.Text = value); }
}
It is sometimes necessary to raise change notification on behalf of another Property in addition to the current one. For example, when the value of a Property is determined by processing the value of another Property.
public string MyText
{
get => _myText;
set
{
RaiseAndUpdate(ref _myText, value);
Raise(nameof(MyUppercaseText));
}
}
public string MyUppercaseText => _myText?.ToUpper();
For those apps built in Xamarin native, where there is no out-of-the-box support for Data Binding, it is possible to use the simple property watcher mechanism instead. The appropriate handlers are typically registered (and cleared down) in the corresponding Activity or ViewController on Android and iOS respectively.
// Register method to handle UI updates when a change notification is raised for that property
_myViewModel.WatchProperty(nameof(MyViewModel.Temperature), UpdateTemperatureIndicator);
...
// Clear the property watchers when the Activity/ViewController no longer needs them
_myViewModel.ClearWatchers();
Primary base class providing the foundations for ViewModels. Provides Properties for IsBusy, IsNotBusy, overridable methods for InitAsync() and Dispose(). Derived from BaseNotifyPropertyChanged.
public class MyViewModel : BaseViewModel
{
public override async Task InitAsync()
{
await DoInitializationWorkAsync();
// Other synchronous initialization work
// e.g. subscribe to event handlers etc.
}
protected override void Dispose(bool disposing)
{
// Clean-up work
// e.g. unsubscribe from event handlers etc.
base.Dispose(disposing);
}
}
There are other specialized ViewModel base classes deriving from BaseViewModel.
BaseCollectionViewModel
Base ViewModel for Views whose primary function is to present a collection or list. In addition to the Properties provided by the BaseViewModel, this provides Properties for Items (ObservableCollection<T>) and SelectedItem along with an abstract LoadItems Task and optional Actions for ItemSelected, Reload, Failed.
BaseNavigationViewModel
Base ViewModel that exposes the INavigationService through the Navigation property.
Defines the contract for NativigationService implementations that enable the navigation between Views from within ViewModels. This supports typical push and pop operations that can attach existing ViewModel instances or create a new one for a given View along with a number of options such as discarding current views, popping to root etc.
Navigate to an existing ViewModel instance
Task ShowModalSettingsAsync()
=> Navigation.PushModalAsync(_settingsViewModel);
Task ShowDetailsAsync()
=> Navigation.PushAsync(_detailViewModel);
Navigate by ViewModel type
Task ShowModalSettingsAsync()
=> Navigation.PushModalAsync<SettingsViewModel>();
Task ShowDetailsAsync()
=> Navigation.PushAsync<DetailViewModel>();
NOTE: The example above assumes that the INavigationService is accessed from a ViewModel that derives from BaseNavigationViewModel with a convenience Navigation Property.
Standalone IValueConverter interface enabling the decoupling of components from the Xamarin.Forms framework allowing for greater portability and re-use.
An ObservableCollection implementation for virtual collections which handles updating the items, VirtualCount, and VirtualPageSize through the method AddPage(IEnumerable<TItem> collection, int? virtualCount = null, int? pageNumber = null, int? pageSize = null)