|This article assumes that the reader is already familair with the MVVM software design pattern. If you are not familair with this design pattern, then it's worth reading up on that first, before proceeding with this article. There are many descriptions of this article, including this one[^]. It is useful to understand the design pattern from a purely conceptual perspective, before looking at the various technical impementations of it. By understanding the design pattern at a conceptual level, you will far easier comprehend its implementation details.
With our latest incarnation of the mobile app now well underway, we have come to the point where we can start building our data entry forms. I have so far implemented the underpinning infrastructure and architecture which enables the app to consume our services, save data to local storage using SQL Lite and send emails from the app. All of this is now fully implemented and working.
We have several data entry forms within our app that allow the user to submit data to our backend services. These include forms for submitting:
- service, repair and MOT bookings
- vehicle inspections
As we have already done so in our previous mobile app, we will be using the MVVM design pattern to implement these data entry forms.
We will impement the data entry forms using XAML and Telerik controls. We could have used the native Xamarin UI controls, but there is a greater selection of Telerik controls, and they provide a consistent API and are easily themable. Although the implementation uses Telerik controls and XAML, the underlying concepts can be applied with any UI technology.
I'll use an example that refers to a simple data entry form that allows a user to enter a message which is sent to the backend service. The message may be to request information for example. This trivial example containing just the one UI control should suffice to demonstrate how the MVVM pattern can be implemented.
I tend to begin the development of a new data entry form from the Model and work backwards from there i.e. Model -> ViewModel -> View.
All Models inherit from the the same base Model class. This base Model class inherits from NotifyPropertyChangedBase which is a Telerik class that supports behaviour similar to INotifyPropertyChanged.
public class BaseFormModel : NotifyPropertyChangedBase
}This ensures that all Models used by the data entry forms will support the ability to raise events when a property on the Model changes. These changes to the Model will be notified to the ViewModel.
Models used by the data entry forms also inherit from the following interface.
public interface IFormData<T>
}By implementing this interface, the Model must therefore contain the method CreateDefaultModel(). This method is used by the ViewModel to supply a default Model (containing default values) which can be used when the View (the XAML form) is first displayed to the user. It implements generics which therefore allows it to work with any type of Model.
Here's the Model for the "Message Us" data entry form. For the purposes of this simple example I have removed much of the code for clarity.
public class MessageUsModel : BaseFormModel, IFormData<MessageUsModel>
private string _messageToSend;
[DisplayOptions(Header = MessageUsModelConstants.MessageHeader)]
public string MessageToSend
get => _messageToSend;
if (_messageToSend == value) return;
_messageToSend = value;
public MessageUsModel CreateDefaultModel()
return new MessageUsModel
_messageToSend = ""
}The decorations on the public property MessageToSend are Telerik specific and define the validation rules / messages for the property. These rules / messages are then enforced by the View. Using this particular implementation of MVVM, the data rules are therefore defined at the level of the Model (which makes sense). Whenever a new value is set on the MessageToSend property, the OnPropertyChanged() event is raised. This updates the state of the ViewModel that is bound to the Model.
Moving onto the ViewModel, we define the base behaviour for all our ViewModels in our base class.
public abstract class ViewModelBase<T> : NotifyPropertyChangedBase where T : new()
public T FormModel = new T();
public abstract Task PostCompleteTask();
}I have used an abstract class that inherits from the same Telerik class as the base Model class i.e. NotifyPropertyChangedBase. The public property FormModel is a reference to the Model. This property is used by the ViewModel when it needs to refer to the Model. The method PostCompleteTask() is invoked by the ViewModel when the form is ready to be submitted. As this is an abstract method, it must therefore be implemented by each inheriting subclass. This provides consistency to all of our ViewModels. The actual work performed by each ViewModel will always be defined within this method.
Here's the ViewModel for the "Message Us" class. For the purposes of this simple example I have removed much of the code for clarity.
public class MessageUsViewModel : ViewModelBase<MessageUsModel>
public MessageUsModel MessageUsModel;
this.MessageUsModel = this.FormModel.CreateDefaultModel();
public override async Task PostCompleteTask()
}The public property MessageUsModel is the reference to our Model. This is initially populated with a default instance in the class constructor by invoking the method CreateDefaultModel() (which we saw earlier) using the public property FormModel (which we also saw earlier).
this.MessageUsModel = this.FormModel.CreateDefaultModel();When the user has finished entering their message and is ready to submit the form, clicking on the form's submit button will invoke the PostCompleteTask() method that will perform whatever processing as necessary (in our case all form data is submitted to our backend services using RESTful Web API services).
Finally, here's the XAML for the View and the code-behind.
public partial class MessageUsView : ContentPage
public MessageUsViewModel Muvm;
this.Muvm = new MessageUsViewModel();
this.BindingContext = this.Muvm;
private async void DataFormValidationCompleted(object sender, FormValidationCompletedEventArgs e)
dataForm.FormValidationCompleted -= this.DataFormValidationCompleted;
private void CommitButtonClicked(object sender, EventArgs e)
dataForm.FormValidationCompleted += this.DataFormValidationCompleted;
}And the XAML code.
<input:RadDataForm x:Name="dataForm" CommitMode="Immediate" />
<input:RadButton x:Name="CommitButton" Text="Save" Clicked="CommitButtonClicked" IsEnabled="True"/>The important parts to note are the setting up of the binding between the View and the ViewModel in the constructor. This sets up the two-way binding, such that any changes in the View are reflected in the ViewModel and vice-versa. These changes are also reflected in the underlying Model (if that wasn't already clear).
this.Muvm = new MessageUsViewModel();
this.BindingContext = this.Muvm;When the user clicks the Submit button, the actions implemented within the ViewModel's PostCompleteTask() method are invoked.
This is a fairly simple example. In a real world use case there would undoubtedly be more complexity, but this should serve as a useful example of using the MVVM design pattern within a Xamarin mobile app. The fact that we are using Telerik UI controls doesn't change the core concepts discussed. The MVVM design pattern is a very powerful design pattern that is perfect for use within data entry forms.
"There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies. The first method is far more difficult." - C.A.R. Hoare