Introducción ¿Qué es MVVM?
Aunque existe muchísima información valiosa en internet sobre el patrón de arquitectura MVVM, me gustaría proporcionar mi visión y definición después de implementarlo en diferentes proyectos personales y empresariales durante bastante tiempo.
MVVM, Model-View-ViewModel, es un patrón de diseño que nos facilita la separación de responsabilidades de la logíca de negocio y de la de presentación de una aplicación, permitiendo una mejor mantenibilidad, testeabilidad y escalibilidad de nuestro código.
Conviene recordar que es un patrón de diseño por lo que es independiente de los frameworks que utilices para desarrollar tu frontal o aplicación, por lo que esto que detallo a continuación es válido y aplica para aplicaciones en Xamarin Forms, WPF, .NET MAUI, Android Kotlin, etc…
Componentes de MVVM y sus responsabilidades
- Model
- Es el componente que representa la “capa de datos” de nuestra aplicación.
- Responsabilidades
- Es el componente encargado de gestionar los datos de la aplicación a través de servicios externos, bases de datos, etc…
- Es el componente encargado de definir las reglas de negocio y la lógica de validación.
Ojo! Este componente NO DEBE tener ninguna lógica relacionada con la interfaz de usuario.
- View
- Es el componente que respresenta la interfaz de usuario (UI) de nuestra aplicación.
- Responsabilidades
- Es el componente encargado de definir los diferentes elementos/ componentes de la UI.
- Es el componente encargado de mostrar datos al usuario y capturar las posibles interacciones del usuario, por ejemplo a través de hacer tap en botones, etc…
Se vincula con los ViewModels a través de comandos y propiedades para manejar dichas interacciones.
- ViewModel
- Es el componente que actua de intermediario entre el Model y la View.
- Es el componente que gestiona la lógica de presentación y maneja la comunicación entre la vista y el modelo.
- Responsabilidades
- Es el componente que expone los datos del Model para que la View pueda utilizarlos/ consumirlos.
- Es el componente que contiene la lógica de presentación tales como comandos y métodos que responden a acciones del usuario.
- Es el componente que maneja la lógica de navegación.
Implementará la interface INotifiyPropertyChanged, es decir, implementará la notificación de cambios.
Para manejar las interacciones del usuario, como por ejemplo hacer tap en un botón o similar, podremos usar los comandos implementando la interface ICommand.
Beneficios de usar MVVM
- Separación de responsabilidades. Como he he comentado antes, facilita la separación de la lógica de negocio de la de presentación.
- Testeabilidad. Facilita la posibilidad de añadir test unitarios a nuestra lógica de negocio e incluso a la de presentación.
- Reusabilidad. Un mismo ViewModel puede utilizarse en diferentes Views ya que no deberían estar acoplados a la UI.
- Mantenibilidad. Por todo lo anterior, hace que nuestro código sea más fácil de mantener y extender.
¿Cómo implementar MVVM en .NET MAUI?
La verdad, tenemos múltiples opciones para implementar MVVM, ya que como he comentado antes el punto fundamental es que nuestros ViewModels implementen la notificación de cambios. ¿Y esto como lo podemos consegir?
- Si vamos en crudo, simplemente tenemos que implementar la inferface INotifyPropertyChanged.
Este será el caso de uso que utilizaremos para este artículo.
- Si no vamos en crudo y nos apoyamos en algún framework o “kit de herramientas” tenemos que implementar en el caso de CommunityToolkit.Mvvm la clase abstracta ObservableObject.
Este caso de uso lo veremos en el siguiente artículo.
Opción “en crudo” implementando la interface INotifyPropertyChanged
Para dotar de algo más de contexto y ver un caso de uso “real”, nuestra aplicación en .NET MAUI consuminará una API, en concreto la de SpaceX y los diferentes lanzamientos al espacio que ha habido a lo largo del tiempo, y tendrá una View principal llamada LaunchesView en la que se mostrará una lista de lanzamientos y haciendo tap en cualquiera de ellos navegará al detalle de los mismos (LaunchDetailView).
Por lo que vamos a implementar en nuestros ViewModels la interface INotifyPropertyChanged a través de una clase abstracta que sea nuestro BaseViewModel de la cual puedan heredar ambos ViewModels (LaunchesViewModel, LaunchDetailViewModel).
public abstract class BaseViewModel : INotifyPropertyChanged
{
// Es el evento que implementa la interface INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
//Lanzamos el evento cada vez que una propiedad de nuestros ViewModels cambie
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Como había comentado, tendremos 2 ViewModels los cuales herederán de nuestro BaseViewModel.
- LaunchesViewModel
- Tendrá una propiedad llamada LaunchesResponse que será utilizada por la vista y que notificará a través de OnPropertyChanged sus posibles cambios de valores.
- Tendrá un comando llamado GoToDetailCommand que será el encargado de gestionar cuando el usuario hace tap sobre uno de los lanzamientos en este ViewModel y que implementará la interface ICommand.
public class LaunchesViewModel : BaseViewModel
{
private ObservableCollection<GetLaunchesResponse> _launchesResponse;
//Definición de nuestra Property
public ObservableCollection<GetLaunchesResponse> LaunchesResponse
{
get { return _launchesResponse; }
set
{
if (_launchesResponse != value)
{
_launchesResponse = value;
OnPropertyChanged();
}
}
}
//Definición de nuestro comando
public ICommand GoToDetailCommand { private set; get; }
}
- LaunchDetailViewModel
- Tendrá 2 propiedades, Title para manejar el título de nuestra vista y Launch que será el propio lanzamiento que le llegará como parámetro a través de la navegación y que sus detalles se utilizarán para mostrar los detalles del lanzamiento en la vista.
¿Cómo podemos pasar parámetros a un ViewModel? Podemos implementar la interface IQueryAttributable, que nos ofrece MAUI (Microsoft.Maui.Controls), en nuestro ViewModel la cual nos obligará a tener un método llamado ApplyQueryAttributes.
public class LaunchDetailViewModel : BaseViewModel, IQueryAttributable
{
private Launch _launch;
//Definición de una de nuestras Properties
public Launch Launch
{
get { return _launch; }
set
{
if (_launch != value)
{
_launch = value;
OnPropertyChanged();
}
}
}
public void ApplyQueryAttributes(IDictionary<string, object> query)
{
_coreService.ShowLoading();
Launch = query["param"] as Launch;
Title = $"Details for {Launch.MissionName}";
_coreService.HideLoading();
}
}
En .NET MAUI o Xamarin Forms, la comunicación entre nuestros ViewModels y las Views se realiza principalmente a través de data binding, y este es el que permite vincular propiedades y comandos de los ViewModels a elementos de la interfaz de usuario (UI) definidos en la View.
- Cada View tendrá un BindingContext que en nuestro caso será el propio ViewModel
- Será a través del Binding donde vinculemos la propiedad o comando de nuestro ViewModel al control/ acción de nuestra UI.
Ojo! En algunos escenarios dónde la comunicación sea algo más compleja, como por ejemplo entre ViewModels, se puede utilizar la clase MessagingCenter que implementa el patrón publicación-suscripción (Pub/ Sub).
<VerticalStackLayout BindableLayout.ItemsSource="{Binding LaunchesResponse}">
<controls:CustomLabel Title="Mission name: " Text="{Binding Launch.MissionName}" />
<TapGestureRecognizer Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:LaunchesViewModel}}, Path=GoToDetailCommand}" CommandParameter="{Binding .}" />
El código de los ViewModels que he incluído no está completo, he omitido los constructores y algunos campos/fields.
Dejo por aquí el repositorio en GitHub por si quieres echarle un vistazo y tener todo el código de ejemplo.
Enlaces de interés
Info | Enlace |
---|---|
Repo en GitHub de CommunityToolkit.Mvvm | https://github.com/CommunityToolkit/dotnet |
Imagen de MVVM | https://github.com/ahmedeltaher/MVVM-Kotlin-Android-Architecture |