MVVM Pattern Explained
Design patterns are proven templates that we can use in our projects. The MVVM pattern is one of the most popular patterns you may encounter in your programming career.
Design patterns are proven templates that we can use in our projects. The MVVM pattern is one of the most popular patterns you may encounter in your programming career.
In this article, I’ve included references to mobile applications, but the pattern itself is technology-independent.
Let’s dive in.
The Concept of the MVVM Pattern
Its full name is Model – View – ViewModel. The pattern is based on separating the system into three logical layers. This approach ensures a solid division of responsibilities between classes. As a result, each layer has its own limitations, which in turn allows us to write better code.
Model
Let’s start with the model layer. This is the lowest layer, where you’ll find the data and business rules of the application. It includes classes responsible for tasks such as working with databases and performing complex operations like API communication. This means that the application’s domain and infrastructure are located here.
Application domain
What is a domain? In a nutshell, it reflects what our application is about. Let’s say you’ve decided to outdo eBay and are creating your own store. In this case, example entities might include: Order, Client, Product.
What’s important here is that when creating these entities, you need to ensure they remain clean. This means you should only use built-in types or other already existing domain types. Ideally, these types should not directly handle data access. They should, in a sense, reflect business rules, but should not contain the logic for handling low-level operations.
Tasks like retrieving user data or saving to a database are not appropriate for entities. Such responsibilities should be placed in repositories or services—essentially, in the infrastructure layer. Domain entities should, if absolutely necessary, rely on abstractions. Even better, it would be ideal to invert the dependency, so that domain services operate on domain models—but that’s a topic for another post.
class User
{
public User(string fullName, int age)
{
FullName = fullName;
Age = age;
}
public string FullName { get; }
public int Age { get; }
}
Data Access
The data access layer is responsible for (you guessed it!) accessing data. This layer includes repositories (classes responsible for returning data through domain models from one or more sources) as well as infrastructure or domain services that operate on and process data. To simplify, you could say that whatever the view model (the layer above) operates on can be considered part of this layer.
What should you keep in mind?
The view has a reference to the view model, not the other way around. Under no circumstances should you pass an instance of the view (or any of its components) to the view model.
Commands
Communication with the view model layer occurs through commands and data binding. The purpose of commands is to trigger an action. The view can only invoke such a command (using the Execute()
method) or, in an extended version, check if the action can be executed (using the CanExecute()
method). An example? The simplest button. It always triggers some action and can be in one of two states: disabled or enabled.
A command is a regular class that contains the two previously mentioned methods and the ability to pass an action (delegate) that will be executed when the command is requested to do so.
From the view's perspective, it is unknown which method will be executed (delegates, as I discussed in another post). The view can only instruct the command to execute it. This serves to encapsulate the code, hiding the implementation within a specific context.
An example? The simplest button. It always triggers some action and can be in one of two states: disabled or enabled.
public ICommand LoginCommand => new Command(NavigateToMainScreen);
// NavigateToMainScreen jest delegatem, który zostanie wywołany.
In fact, nothing bad will happen if you simply call a method of the view model directly from the view. A command doesn't have to be the only solution.
Data binding
It enables continuous communication between the view model and the view. Thanks to it (or rather the framework that provides it), we don’t have to manually refresh data. It’s enough to correctly bind one of the view’s properties to a specific property in the view model according to the framework’s requirements. Our responsibility is simply to notify the view model that the data has changed. The binding takes care of the rest.
var set = this.CreateBindingSet<LoginView, LoginPO>();
set.Bind(_emailTextView)
.For(v => v.Text)
.To(po => po.Email);
set.Bind(_passwordTextView)
.For(v => v.Text)
.To(po => po.Password);
set.Apply();
View model
The final component is the view model layer. Its role is to provide data to the view and exchange information with the model. This means fetching and updating data. The view model itself provides and contains only what the view needs.
It is a tailored provider of content and behaviors specifically for the view.
Let's assume you're creating a login screen. In that case, this layer will include properties that store the currently entered email and password. Additionally, you'll also add the previously mentioned command for the login action.
We need to ensure that the view has access to up-to-date data. To handle this, you should use the INotifyPropertyChanged
interface (or its equivalent, depending on the framework), which, as the name suggests, is responsible for notifying about changes. This interface is used in the view for refreshing it (see data binding). As a developer, your job is simply to ensure that changes are properly notified.
MVVM Pattern—What do you think about it?
How many projects have you worked on that used this pattern? In my case, I primarily used the MVVM pattern in Xamarin.Native projects, and in my opinion, it’s a foundational pattern when working with this technology.
Do you have any thoughts? Feel free to share them in the comments.