Software Crafters ® | Creado con 🖤 para elevar el nivel de la conversación sobre programación en español| Legal
En el anterior artículo vimos una breve introducción al patrón de diseño MVVM. Tal como decía en ese post, está considerado una buena práctica el uso de dicho patrón a la hora de desarrollar un proyecto, tanto con Xamarin tradicional, como con Xamarin Forms. El objetivo de esta entrada es continuar profundizando en el desarrollo con Xamarin Forms aplicando MVVM.
En el año 2004, un grupo de desarrollo de Microsoft trabajaba en un proyecto denominado "Avalon", más conocido por su nombre definitivo WPF (Windows Presentation Foundation). El propósito de dicho proyecto era permitir el desarrollo de aplicaciones de escritorio más completas y con un aspecto visual mucho más logrado y complejo de lo que era posible con Windows Forms.
Al año siguiente John Gossman (miembro del equipo de desarrollo de "Avalon"), en un artículo de la MSDN, mostraba al público el patrón MVVM. En el artículo, MVVM se presenta como una variación del patrón MVC ajustado a "WPF" y a su sistema de enlace a datos, aunque realmente es una adaptación del patrón "presentation model" creado por el mítico Martin Fowler.
La finalidad principal del patrón MVVM (Modelo Vista Vista-Modelo) es tratar de desacoplar lo máximo posible la interfaz de usuario de la lógica de la aplicación. Veamos a grandes rasgos sus partes principales:
Representa la capa de datos y/o la lógica de negocio, también denominado como el objeto del dominio. El modelo contiene la información, pero nunca las acciones o servicios que la manipulan. En ningún caso tiene dependencia alguna con la vista.
La misión de la vista es representar la información a través de los elementos visuales que la componen. Las vistas en MVVM son activas, contienen comportamientos, eventos y enlaces a datos que, en cierta manera, necesitan tener conocimiento del modelo subyacente. En Xamarin Forms podemos crear nuestras interfaces a través de código C# o XAML.
El ViewModel (modelo de vista) es un actor intermediario entre el modelo y la vista, contiene toda la lógica de presentación y se comporta como una abstracción de la interfaz. La comunicación entre la vista y el viewmodel se realiza por medio los enlaces de datos (binders).
Vamos a tomar como punto de partida el ejemplo visto en la entrada anterior, la idea es añadir a dicho ejemplo un elemento entry en el cual el usuario pueda introducir un texto y a su vez este se vaya reflejando en un elemento de tipo label.
Contábamos con una estructura MVVM lo más sencilla posible, teníamos un ViewModel denominado MainViewModel con una propiedad llamada “MyMessage":
public class MainViewModel { private string _myMessage; public MainViewModel() { Message = "Hello MVVM!"; } public string MyMessage { get { return _myMessage; } set { _myMessage = value; } } }
Además, contabamos con una vista que utilizaba como contexto "MainViewModel" enlazando desde el atributo "text" de un label a la propiedad "MyMessage":
//XAML <?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="simpleMVVM.Views.MainView"> <Label Text="{Binding MyMessage}" VerticalOptions="Center" HorizontalOptions="Center" /> </ContentPage> //Code behind namespace simpleMVVM.Views { public partial class MainView : ContentPage { public MainView() { InitializeComponent(); BindingContext = new MainViewModel(); } } }
Antes de continuar con el ejemplo veamos como funcionan los modos de enlace a datos. En Xamarin Forms el modo de enlace a datos se define con la palabra reservada "mode", la cual nos indica como se comporta el mismo. Contamos con los siguientes
Continuando con el ejemplo, vamos a añadir un elemento de tipo entry, que esté enlazado a la propiedad "myMessage" de forma bidireccional, un elemento label que está enlazado con el modo por defecto (oneWay) y un stacklayout que haces las veces de contenedor, debido a que las páginas no pueden contener más de un elemento.
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="simpleMVVM.Views.MainView"> <StackLayout> <Entry Text="{Binding MyMessage, Mode=TwoWay}" VerticalOptions="Center" HorizontalOptions="Center" /> <Label Text="{Binding MyMessage}" VerticalOptions="Center" HorizontalOptions="Center" /> </StackLayout> </ContentPage>
Al hacer debug sobre el código anterior, observarás que aparentemente todo está funcionando correctamente. Si examinas el contenido de la propiedad My
Message
verás que el valor se ha actualizado correctamente, pero el texto del elemento label no se actualiza, esto es debido a que no se ha notificado a la vista que el valor de la propiedad ha cambiado. Es aquí donde entra en juego la interfaz INotifyPropertyChanged.
La interfaz INotifyPropertyChanged define un método llamado RaisePropertyChanged y un evento llamado PropertyChanged, que debemos implementar**.** Dicho evento será lanzado cuando se actualice el valor de la propiedad deseada del ViewModel y notificará a la View que evaluará de nuevo el valor de dicha propiedad. Para que esto funcione correctamente es necesario ejecutar el método RaisePropertyChanged en el setter de la propiedad.
public class MainViewModel { private string _myMessage; public MainViewModel() { Message = "Hello MVVM!"; } public string MyMessage { get { return _myMessage; } set { _myMessage = value; RaisePropertyChanged("MyMessage"); } } private void RaisePropertyChanged(string propertyName) { var handle = PropertyChanged; if (handle != null) handle(this, new PropertyChangedEventArgs(propertyName)); } public event PropertyChangedEventHandler PropertyChanged; }
Dado que todos los ViewModels o la mayoría, van a hacer uso de la interfaz INotifyPropertyChanged, sería interesante crear un ViewModel base, del cual hereden demás. Por otro lado, vamos a hacer uso del atributo
CallerMemberName
con el cual aseguras que el nombre de la propiedad que llama al método RaisePropertyChanged es el correcto sin tener que indicarlo explícitamente.
public class ViewModelBase { private void RaisePropertyChanged([CallerMemberName] string propertyName = null) { var handle = PropertyChanged; if (handle != null) handle(this, new PropertyChangedEventArgs(propertyName)); } public event PropertyChangedEventHandler PropertyChanged; }
Tras refactorizar el
MainViewModel
quedaría tal que así:
public class MainViewModel : ViewModelBase { private string _myMessage; public MainViewModel() { } public string MyMessage { get { return _myMessage; } set { _myMessage = value; RaisePropertyChanged(); } } }
Este sería el resultado, tras ejecutar el proyecto:
Nos faltaría por ver la interacción a priori más básica que puede realizar un usuario sobre una app, pulsar un botón y que ocurra algo. Para llevar a cabo esta funcionalidad se aplica el patrón Command (comando) cuyo objetivo no es otro que encapsular la invocación de un método de otro objeto.
"Debes depender de abstracciones, no de concreciones"
Para aplicar este mecanismo en Xamarin.Forms junto con MVVM el ViewModel asociado a la vista debe exponer una propiedad que implemente la interfaz
ICommand
. El valor de dicha propiedad puede ser asignado a elementos visuales como botones, a través de la propiedad comando vía enlace a datos. Esto a su vez ejecutará el método Execute de dicha interfaz, la cual además define un método CanExecute que permite verificar si el comando puede ser ejecutado o no.
Al igual que ocurría con la implementación de la interfaz INotifyPropertyChanged, cuando utilizamos comandos se tiende a repetir más código del necesario, por esta razón se utilizan implementaciones reutilizables como DelegateCommand. Esta implementación es una clase que implementa la intefaz ICommnad que recibe dos parámetros en su constructor del tipo Action y Func, el método a ejecutar (Execute) y el método que indica si se puede ejecutar o no(CanExecute), respectivamente.
public class DelegateCommand : ICommand { private Action _execute; private Func<bool> _canExecute; public DelegateCommand(Action execute, Func<bool> canExecute = null) { _execute = execute; _canExecute = canExecute; } public bool CanExecute(object parameter) { if (_canExecute == null) return true; return _canExecute(); } public event EventHandler CanExecuteChanged; public void Execute(object parameter) { if (_execute != null) _execute(); } public void RaiseCanExecuteChanged() { var handle = CanExecuteChanged; if (handle != null) handle(this, new EventArgs()); } }
Si no estás muy familiarizado con C#, los tipos
Action
y Func<bool>
, son delegados genéricos. Action
simplemente es un delegado que no devuelve nada y en este caso tampoco recibe ningún parametro; y Func<bool>
, tampoco tiene parámetros y en este caso devuelve un booleano. Por sino lo sabes, un delegado no es más que un tipo de dato que representa un puntero a un método.
Continuando con el ejemplo, vamos a añadir a la view del ejemplo anterior un botón, el cual al ser pulsado incrementará un contador que se mostrará en el elemento label de la misma. Para ello se le asigna el commando MyCommand a la propiedad command del botón, el cual se implimentará en el correspondiente ViewModel.
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="simpleMVVM.Views.MainView"> <StackLayout> <Button Text="Sent" Command="{Binding MyCommand}" VerticalOptions="Center" HorizontalOptions="Center" /> <Label Text="{Binding MyMessage}" VerticalOptions="Center" HorizontalOptions="Center" /> </StackLayout> </ContentPage>
En el
MainViewModel
se crea una propiedad del tipo ICommand
denominada MyCommand, cuya variable privada será del tipo DelegateCommand
que hemos implementado con anterioridad. En el getter de esta propiedad, se instanciará DelegateCommand recibiendo como parámetro el método que contiene la lógica para incrementar el contador, quedando el MainViewModel
tal que así:
public class MainViewModel : ViewModelBase { private int _counter; private DelegateCommand _myCommand; void counterCommandExecute() { _counter++; RaisePropertyChanged("MyMessage"); } public MainViewModel() { _counter = 0; } public string MyMessage { get { return string.Format("{0} times", _counter); } } public ICommand MyCommand { get { return _myCommand = _myCommand ?? new DelegateCommand(counterCommandExecute); } } }
Tras ejecutar la app se puede observar que al pulsar el botón el contador se va incrementando correctamente:
En este artículo se han expuesto los conceptos básicos para aplicar el patrón MVVM a la hora de realizar las interacciones básicas de los usuarios con Xamarin Forms, donde destacan las notificaciones y los comandos.
Continuaré profundizando en próximas entradas en el desarrollo con Xamarin, seguiré exponiendo conceptos fundamentales como la navegación entre páginas, contenedores de dependencias, persistencia de datos en el dispositivo, consumo de APIS, etc.
Si te ha gustado la entrada valora y comparte en tus redes sociales. No dudes en comentar dudas, aportes o sugerencias, estaré encantado de responder.
Este artículo se distribuye bajo una Licencia Creative Commons Reconocimiento-CompartirIgual 4.0 Internacional (CC BY-SA 4.0)